dusen 0.1.0 → 0.2.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.
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