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 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
+