searcher 0.0.1

Sign up to get free protection for your applications and to get access to all the features.
data/.gitignore ADDED
@@ -0,0 +1,4 @@
1
+ pkg/*
2
+ *.gem
3
+ .bundle
4
+ tmp/*.log
data/Gemfile ADDED
@@ -0,0 +1,9 @@
1
+ source :gemcutter
2
+
3
+ group(:test) do
4
+ gem 'sqlite3-ruby'
5
+ gem 'rspec'
6
+ end
7
+
8
+ # Specify your gem's dependencies in searcher.gemspec
9
+ gemspec
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,2 @@
1
+ require 'bundler'
2
+ Bundler::GemHelper.install_tasks
@@ -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
@@ -0,0 +1,3 @@
1
+ module Searcher
2
+ VERSION = "0.0.1"
3
+ 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
@@ -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
+