dusen 0.1.0 → 0.2.0

Sign up to get free protection for your applications and to get access to all the features.
data/.gitignore CHANGED
@@ -4,5 +4,6 @@ pkg
4
4
  .idea
5
5
  app_root/log/*
6
6
  Gemfile.lock
7
+ spec/shared/app_root/db/*.db
7
8
 
8
9
 
data/README.md CHANGED
@@ -1,4 +1,162 @@
1
- Dusen - Maps Google-like queries to ActiveRecord scope chains
2
- =============================================================
1
+ Dusen - Maps Google-like queries to ActiveRecord scopes
2
+ =======================================================
3
3
 
4
- TODO
4
+
5
+ Dusen gives your ActiveRecord models a DSL to process Google-like queries like:
6
+
7
+ some words
8
+ "a phrase of words"
9
+ filetype:pdf
10
+ a mix of words "and phrases" and qualified:fields
11
+
12
+ Dusen tokenizes these queries for you and feeds them through simple mappers that
13
+ convert a token to an ActiveRecord scope chain.
14
+ This process is packaged in a class method `.search`:
15
+
16
+ Contact.search('makandra software "Ruby on Rails" city:augsburg')
17
+
18
+
19
+ Installation
20
+ ------------
21
+
22
+ In your `Gemfile` say:
23
+
24
+ gem 'dusen'
25
+
26
+ Now run `bundle install` and restart your server.
27
+
28
+
29
+
30
+ Processing text queries
31
+ -----------------------
32
+
33
+ This describes how to define a search syntax that processes queries
34
+ of words and phrases:
35
+
36
+ coworking fooville "market ave"
37
+
38
+
39
+ Our example will be a simple address book:
40
+
41
+ class Contact < ActiveRecord::Base
42
+
43
+ validates_presence_of :name, :street, :city, :name
44
+
45
+ end
46
+
47
+
48
+ We will now teach `Contact` to process a text query like this:
49
+
50
+ class Contact < ActiveRecord::Base
51
+
52
+ ...
53
+
54
+ search_syntax do
55
+
56
+ search_by :text do |scope, phrase|
57
+ columns = [:name, :street, :city, :email]
58
+ scope.where_like(columns => phrase)
59
+ end
60
+
61
+ end
62
+
63
+ end
64
+
65
+
66
+ Note how you will only ever need to deal with a single token (word or phrase) and return a scope that matches the token.
67
+ Dusen will take care how these scopes will be chained together.
68
+
69
+ If we now call `Contact.search('coworking fooville "market ave"')`
70
+ the block supplied to `search_by` is called once per token:
71
+
72
+ 1. `|Contact, 'coworking'|`
73
+ 2. `|Contact.where_like(columns => 'coworking'), 'fooville'|`
74
+ 3. `|Contact.where_like(columns => 'coworking').where_like(columns => 'fooville'), 'market ave'|`
75
+
76
+
77
+ The resulting scope chain is your `Contact` model filtered by
78
+ the given query:
79
+
80
+ > Contact.search('coworking fooville "market ave"')
81
+ => Contact.where_like(columns => 'coworking').where_like(columns => 'fooville').where_like(columns => 'market ave')
82
+
83
+
84
+ Note that `where_like` is an utility method that comes with the Dusen gem.
85
+ It takes one or more column names and a phrase and generates an SQL fragment
86
+ like this:
87
+
88
+ contacts.name LIKE "%coworking%" OR contacts.street LIKE "%coworking%" OR contacts.email LIKE "%coworking%" OR contacts.email LIKE "%coworking%"
89
+
90
+
91
+ Processing queries for qualified fields
92
+ ---------------------------------------
93
+
94
+ Let's give `Contact` a way to explictely search for a contact's email address, without
95
+ going through a full text search. We do this by adding additional `search_by` instructions
96
+ to our model:
97
+
98
+ search_syntax do
99
+
100
+ search_by :text do |scope, phrase|
101
+ ...
102
+ end
103
+
104
+ search_by :email do |scope, email|
105
+ scope.where(:email => email)
106
+ end
107
+
108
+ end
109
+
110
+
111
+ The result is this:
112
+
113
+ > Contact.search('email:foo@bar.com')
114
+ => Contact.where(:email => 'foo@bar.com')
115
+
116
+
117
+ Feel free to combine text tokens and field tokens:
118
+
119
+ > Contact.search('fooville email:foo@bar.com')
120
+ => Contact.where_like(columns => 'fooville').where(:email => 'foo@bar.com')
121
+
122
+
123
+
124
+ Programmatic access without DSL
125
+ -------------------------------
126
+
127
+ You can use Dusen's functionality without using the ActiveRecord DSL or the search scope. Here are some method calls to get you started:
128
+
129
+ Contact.search_syntax # => #<Dusen::Syntax>
130
+
131
+ syntax = Dusen::Syntax.new
132
+ syntax.learn_field :email do |scope, email|
133
+ scope.where(:email => email)
134
+ end
135
+
136
+ query = Dusen::Parser.parse('fooville email:foo@bar.com') # => #<Dusen::Query>
137
+ query.tokens # => [#<Dusen::Token field: 'text', value: 'fooville'>, #<Dusen::Token field: 'email', value: 'foo@bar.com'>]
138
+ query.to_s # => "fooville + foo@bar.com"
139
+
140
+ syntax.search(Contact, query) # => #<ActiveRecord::Relation>
141
+
142
+
143
+ Development
144
+ -----------
145
+
146
+ Test applications for various Rails versions lives in `spec`. You can run specs from the project root by saying:
147
+
148
+ bundle exec rake all:spec
149
+
150
+ If you would like to contribute:
151
+
152
+ - Fork the repository.
153
+ - Push your changes **with specs**.
154
+ - Send me a pull request.
155
+
156
+ I'm very eager to keep this gem leightweight and on topic. If you're unsure whether a change would make it into the gem, [talk to me beforehand](mailto:henning.koch@makandra.de).
157
+
158
+
159
+ Credits
160
+ -------
161
+
162
+ Henning Koch from [makandra](http://makandra.com/)
@@ -2,9 +2,13 @@ module Dusen
2
2
  module ActiveRecord
3
3
 
4
4
  def search_syntax(&dsl)
5
- @dusen_syntax = Dusen::Description.read_syntax(&dsl)
6
- singleton_class.send(:define_method, :search) do |query_string|
7
- @dusen_syntax.search(self, query_string)
5
+ if dsl
6
+ @search_syntax = Dusen::Description.read_syntax(&dsl)
7
+ singleton_class.send(:define_method, :search) do |query_string|
8
+ @search_syntax.search(self, query_string)
9
+ end
10
+ else
11
+ @search_syntax
8
12
  end
9
13
  end
10
14
 
data/lib/dusen/parser.rb CHANGED
@@ -8,23 +8,23 @@ module Dusen
8
8
  def self.parse(query_string)
9
9
  query_string = query_string.dup # we are going to delete substrings in-place
10
10
  query = Query.new
11
- extract_field_query_atoms(query_string, query)
12
- extract_text_query_atoms(query_string, query)
11
+ extract_field_query_tokens(query_string, query)
12
+ extract_text_query_tokens(query_string, query)
13
13
  query
14
14
  end
15
15
 
16
- def self.extract_text_query_atoms(query_string, query)
16
+ def self.extract_text_query_tokens(query_string, query)
17
17
  while query_string.sub!(TEXT_QUERY, '')
18
18
  value = "#{$1}#{$2}"
19
- query << Atom.new(value)
19
+ query << Token.new(value)
20
20
  end
21
21
  end
22
22
 
23
- def self.extract_field_query_atoms(query_string, query)
23
+ def self.extract_field_query_tokens(query_string, query)
24
24
  while query_string.sub!(FIELD_QUERY, '')
25
25
  field = $1
26
26
  value = "#{$2}#{$3}"
27
- query << Atom.new(field, value)
27
+ query << Token.new(field, value)
28
28
  end
29
29
  end
30
30
 
data/lib/dusen/query.rb CHANGED
@@ -4,11 +4,11 @@ module Dusen
4
4
  include Enumerable
5
5
 
6
6
  def initialize
7
- @atoms = []
7
+ @tokens = []
8
8
  end
9
9
 
10
- def <<(atom)
11
- @atoms << atom
10
+ def <<(token)
11
+ @tokens << token
12
12
  end
13
13
 
14
14
  def to_s
@@ -16,7 +16,7 @@ module Dusen
16
16
  end
17
17
 
18
18
  def each(&block)
19
- @atoms.each(&block)
19
+ @tokens.each(&block)
20
20
  end
21
21
 
22
22
  end
data/lib/dusen/syntax.rb CHANGED
@@ -17,9 +17,9 @@ module Dusen
17
17
  def search(root_scope, query)
18
18
  scope = root_scope
19
19
  query = parse(query) if query.is_a?(String)
20
- query.each do |atom|
21
- scoper = @scopers[atom.field] || unknown_scoper
22
- scope = scoper.call(scope, atom.value)
20
+ query.each do |token|
21
+ scoper = @scopers[token.field] || unknown_scoper
22
+ scope = scoper.call(scope, token.value)
23
23
  end
24
24
  scope
25
25
  end
@@ -1,5 +1,5 @@
1
1
  module Dusen
2
- class Atom
2
+ class Token
3
3
 
4
4
  attr_reader :field, :value
5
5
 
data/lib/dusen/version.rb CHANGED
@@ -1,3 +1,3 @@
1
1
  module Dusen
2
- VERSION = '0.1.0'
3
- end
2
+ VERSION = '0.2.0'
3
+ end
data/lib/dusen.rb CHANGED
@@ -1,5 +1,5 @@
1
1
  require 'dusen/util'
2
- require 'dusen/atom'
2
+ require 'dusen/token'
3
3
  require 'dusen/description'
4
4
  require 'dusen/parser'
5
5
  require 'dusen/query'
@@ -34,7 +34,7 @@ describe Dusen::ActiveRecord do
34
34
  User.search('city:"Foo Bar"').to_a.should == [match]
35
35
  end
36
36
 
37
- it 'should allow to mix multiple types of atoms in a single query' do
37
+ it 'should allow to mix multiple types of tokens in a single query' do
38
38
  match = User.create!(:name => 'Foo', :city => 'Foohausen')
39
39
  no_match = User.create!(:name => 'Foo', :city => 'Barhausen')
40
40
  User.search('Foo city:Foohausen').to_a.should == [match]
@@ -42,4 +42,12 @@ describe Dusen::ActiveRecord do
42
42
 
43
43
  end
44
44
 
45
+ describe '.search_syntax' do
46
+
47
+ it "should return the model's syntax definition when called without a block" do
48
+ User.search_syntax.should be_a(Dusen::Syntax)
49
+ end
50
+
51
+ end
52
+
45
53
  end
metadata CHANGED
@@ -1,13 +1,13 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: dusen
3
3
  version: !ruby/object:Gem::Version
4
- hash: 27
4
+ hash: 23
5
5
  prerelease:
6
6
  segments:
7
7
  - 0
8
- - 1
8
+ - 2
9
9
  - 0
10
- version: 0.1.0
10
+ version: 0.2.0
11
11
  platform: ruby
12
12
  authors:
13
13
  - Henning Koch
@@ -15,7 +15,7 @@ autorequire:
15
15
  bindir: bin
16
16
  cert_chain: []
17
17
 
18
- date: 2012-11-20 00:00:00 +01:00
18
+ date: 2012-11-23 00:00:00 +01:00
19
19
  default_executable:
20
20
  dependencies:
21
21
  - !ruby/object:Gem::Dependency
@@ -47,11 +47,11 @@ files:
47
47
  - dusen.gemspec
48
48
  - lib/dusen.rb
49
49
  - lib/dusen/active_record_ext.rb
50
- - lib/dusen/atom.rb
51
50
  - lib/dusen/description.rb
52
51
  - lib/dusen/parser.rb
53
52
  - lib/dusen/query.rb
54
53
  - lib/dusen/syntax.rb
54
+ - lib/dusen/token.rb
55
55
  - lib/dusen/util.rb
56
56
  - lib/dusen/version.rb
57
57
  - spec/rails-2.3/Gemfile
@@ -110,7 +110,6 @@ files:
110
110
  - spec/rails-3.2/spec_helper.rb
111
111
  - spec/shared/app_root/app/controllers/application_controller.rb
112
112
  - spec/shared/app_root/app/models/user.rb
113
- - spec/shared/app_root/db/gem_test.db
114
113
  - spec/shared/app_root/db/migrate/001_create_users.rb
115
114
  - spec/shared/dusen/active_record_spec.rb
116
115
  has_rdoc: true
Binary file