mongoid-tags 0.1.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,25 @@
1
+ # Because this is a gem, ignore Gemfile.lock:
2
+
3
+ Gemfile.lock
4
+
5
+ # And because this is Ruby, ignore the following
6
+ # (source: https://github.com/github/gitignore/blob/master/Ruby.gitignore):
7
+
8
+ *.gem
9
+ *.rbc
10
+ .bundle
11
+ .config
12
+ coverage
13
+ InstalledFiles
14
+ lib/bundler/man
15
+ pkg
16
+ rdoc
17
+ spec/reports
18
+ test/tmp
19
+ test/version_tmp
20
+ tmp
21
+
22
+ # YARD artifacts
23
+ .yardoc
24
+ _yardoc
25
+ doc/
data/CHANGELOG.md ADDED
@@ -0,0 +1,3 @@
1
+ # 0.0.1 (December 22, 2012)
2
+
3
+ * First version.
data/Gemfile ADDED
@@ -0,0 +1,6 @@
1
+ source :rubygems
2
+
3
+ gemspec
4
+
5
+ gem 'minitest'
6
+ gem 'rake'
data/README.md ADDED
@@ -0,0 +1,68 @@
1
+ # mongoid-tags
2
+
3
+ Mongoid::Tags adds a simple tagging system to your Mongoid documents,
4
+ and allows you to query them using a boolean search syntax.
5
+
6
+
7
+ ## Installation
8
+
9
+ In your Gemfile:
10
+
11
+ ```ruby
12
+ gem 'mongoid-tags'
13
+ ```
14
+
15
+ ## Usage
16
+
17
+ ```ruby
18
+ class Document
19
+ include Mongoid::Document
20
+ include Mongoid::Tags
21
+ end
22
+ ```
23
+
24
+ ```ruby
25
+ # Documents tagged foo || bar
26
+ Document.tagged('foo bar')
27
+
28
+ # Documents tagged foo && bar
29
+ Document.tagged('+foo bar')
30
+
31
+ # Documents tagged foo, but !bar
32
+ Document.tagged('foo -bar')
33
+ ```
34
+
35
+ Be sure to checkout spec/integration_spec.rb for more examples. By the way, `tagged` returns a `Mongoid::Criteria` object so you can chain it to your existing criteria, e.g: `Document.where(published: true).tagged('foo').desc(:created_at)`
36
+
37
+ ## Contributing
38
+
39
+ 1. Fork it
40
+ 2. Create your feature branch (`git checkout -b my-new-feature`)
41
+ 3. Commit your changes (`git commit -am 'Added some feature'`)
42
+ 4. Push to the branch (`git push origin my-new-feature`)
43
+ 5. Create new Pull Request
44
+
45
+ ## Copyright
46
+
47
+ (The MIT license)
48
+
49
+ Copyright (c) 2012 Mario Uher
50
+
51
+ Permission is hereby granted, free of charge, to any person obtaining
52
+ a copy of this software and associated documentation files (the
53
+ "Software"), to deal in the Software without restriction, including
54
+ without limitation the rights to use, copy, modify, merge, publish,
55
+ distribute, sublicense, and/or sell copies of the Software, and to
56
+ permit persons to whom the Software is furnished to do so, subject to
57
+ the following conditions:
58
+
59
+ The above copyright notice and this permission notice shall be
60
+ included in all copies or substantial portions of the Software.
61
+
62
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
63
+ EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
64
+ MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
65
+ NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
66
+ LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
67
+ OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
68
+ WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
data/Rakefile ADDED
@@ -0,0 +1,8 @@
1
+ require 'rake'
2
+ require 'rake/testtask'
3
+
4
+ Rake::TestTask.new do |t|
5
+ t.test_files = Dir.glob('spec/**/*_spec.rb')
6
+ end
7
+
8
+ task(default: :test)
@@ -0,0 +1,5 @@
1
+ module Mongoid
2
+ module Tags
3
+ VERSION = '0.1.1'
4
+ end
5
+ end
@@ -0,0 +1,69 @@
1
+ require 'active_support'
2
+ require 'mongoid'
3
+ require 'treetop'
4
+
5
+ module Mongoid
6
+ module Tags
7
+ extend ActiveSupport::Concern
8
+
9
+ included do |base|
10
+ field :tags, type: Array, default: []
11
+ index tags: 1
12
+ end
13
+
14
+ module ClassMethods
15
+ def selector(query)
16
+ parser.parse(query).tap do |result|
17
+ raise Error, parser.failure_reason unless result
18
+ end.to_criteria
19
+ end
20
+
21
+ def tagged(query)
22
+ where(tags: selector(query))
23
+ end
24
+
25
+ private
26
+ def parser
27
+ @parser ||= Treetop.load(File.expand_path('../tags.tt', __FILE__)).new
28
+ end
29
+ end
30
+
31
+ class Query < Treetop::Runtime::SyntaxNode
32
+ def to_criteria(tags = nil)
33
+ {}.tap do |criteria|
34
+ (tags.presence || elements).each do |tag|
35
+ (criteria[tag.operator.selector] ||= []) << tag.to_criteria if tag.is_a? Tag
36
+
37
+ criteria.merge!(to_criteria(tag.elements)) { |key, first, second| first + second } if tag.elements.present?
38
+ end
39
+ end
40
+ end
41
+ end
42
+
43
+ class Tag < Treetop::Runtime::SyntaxNode
44
+ def to_criteria
45
+ tag.text_value
46
+ end
47
+ end
48
+
49
+ class OrOperator < Treetop::Runtime::SyntaxNode
50
+ def selector
51
+ '$in'
52
+ end
53
+ end
54
+
55
+ class AndOperator < Treetop::Runtime::SyntaxNode
56
+ def selector
57
+ '$all'
58
+ end
59
+ end
60
+
61
+ class NotOperator < Treetop::Runtime::SyntaxNode
62
+ def selector
63
+ '$nin'
64
+ end
65
+ end
66
+
67
+ class Error < StandardError; end
68
+ end
69
+ end
@@ -0,0 +1,33 @@
1
+ module Mongoid
2
+ module Tags
3
+ grammar Tags
4
+ rule query
5
+ tag (space tag)* <Query>
6
+ end
7
+
8
+ rule tag
9
+ operator tag:([a-zA-Z0-9] [a-zA-Z0-9_]*) <Tag>
10
+ end
11
+
12
+ rule operator
13
+ and_operator / not_operator / or_operator
14
+ end
15
+
16
+ rule or_operator
17
+ '' <OrOperator>
18
+ end
19
+
20
+ rule and_operator
21
+ '+' <AndOperator>
22
+ end
23
+
24
+ rule not_operator
25
+ '-' <NotOperator>
26
+ end
27
+
28
+ rule space
29
+ [\s]+
30
+ end
31
+ end
32
+ end
33
+ end
@@ -0,0 +1,18 @@
1
+ require './lib/mongoid/tags/version'
2
+
3
+ Gem::Specification.new do |gem|
4
+ gem.name = 'mongoid-tags'
5
+ gem.version = Mongoid::Tags::VERSION
6
+ gem.authors = 'Mario Uher'
7
+ gem.email = 'uher.mario@gmail.com'
8
+ gem.homepage = 'https://github.com/haihappen/mongoid-tags'
9
+ gem.summary = 'Simple tagging system with boolean search.'
10
+ gem.description = 'Mongoid::Tags adds a simple tagging system to your Mongoid documents, and allows you to query them using a boolean search syntax.'
11
+
12
+ gem.files = `git ls-files`.split("\n")
13
+ gem.require_path = 'lib'
14
+
15
+ gem.add_dependency 'activesupport'
16
+ gem.add_dependency 'mongoid'
17
+ gem.add_dependency 'treetop'
18
+ end
@@ -0,0 +1,121 @@
1
+ require File.expand_path('../spec_helper', __FILE__)
2
+
3
+ describe 'Mongoid::Tags integration' do
4
+ before do
5
+ # Create all possible combinations
6
+ 3.times do |i|
7
+ %w[foo bar baz].combination(i + 1).each do |tags|
8
+ Document.create(tags: tags)
9
+ end
10
+ end
11
+ end
12
+
13
+
14
+ let(:query) { self.class.name.demodulize } # Use description as query ;)
15
+ subject { Document.tagged(query) }
16
+
17
+
18
+ describe 'foo' do
19
+ it 'returns documents including foo' do
20
+ subject.all? { |d| d.tags.include?('foo') }.must_equal true
21
+ end
22
+ end
23
+
24
+
25
+ describe '+foo' do
26
+ it 'returns documents including at least foo' do
27
+ subject.all? { |d| d.tags.include?('foo') }.must_equal true
28
+ end
29
+ end
30
+
31
+
32
+ describe '-foo' do
33
+ it 'returns documents not including foo' do
34
+ subject.none? { |d| d.tags.include?('foo') }.must_equal true
35
+ end
36
+ end
37
+
38
+
39
+ describe 'foo bar' do
40
+ it 'returns documents including foo or bar' do
41
+ subject.all? { |d| d.tags.include?('foo') || d.tags.include?('bar') }.must_equal true
42
+ end
43
+ end
44
+
45
+
46
+ describe '+foo bar' do
47
+ it 'returns documents including foo and bar' do
48
+ subject.all? { |d| d.tags.include?('foo') }.must_equal true
49
+ subject.all? { |d| d.tags.include?('bar') }.must_equal true
50
+ end
51
+ end
52
+
53
+
54
+ describe '-foo bar' do
55
+ it 'returns documents not including foo, but bar' do
56
+ subject.none? { |d| d.tags.include?('foo') }.must_equal true
57
+ subject.all? { |d| d.tags.include?('bar') }.must_equal true
58
+ end
59
+ end
60
+
61
+
62
+ describe '-foo -bar' do
63
+ it 'returns documents not including foo and bar' do
64
+ subject.none? { |d| d.tags.include?('foo') }.must_equal true
65
+ subject.none? { |d| d.tags.include?('bar') }.must_equal true
66
+ end
67
+ end
68
+
69
+
70
+ describe 'foo bar baz' do
71
+ it 'returns documents including foo or bar' do
72
+ subject.all? { |d| d.tags.include?('foo') || d.tags.include?('bar') || d.tags.include?('baz') }.must_equal true
73
+ end
74
+ end
75
+
76
+
77
+ describe '+foo bar baz' do
78
+ it 'returns documents including at least foo and bar or baz' do
79
+ subject.all? { |d| d.tags.include?('foo') }.must_equal true
80
+ subject.all? { |d| d.tags.include?('bar') || d.tags.include?('baz') }.must_equal true
81
+ end
82
+ end
83
+
84
+
85
+ describe '+foo +bar baz' do
86
+ it 'returns documents including at least foo, bar and baz' do
87
+ subject.all? { |d| d.tags.include?('foo') }.must_equal true
88
+ subject.all? { |d| d.tags.include?('bar') }.must_equal true
89
+ subject.all? { |d| d.tags.include?('baz') }.must_equal true
90
+ end
91
+ end
92
+
93
+
94
+ describe '-foo bar baz' do
95
+ it 'returns documents not including foo, but bar or baz' do
96
+ subject.none? { |d| d.tags.include?('foo') }.must_equal true
97
+ subject.all? { |d| d.tags.include?('bar') || d.tags.include?('baz') }.must_equal true
98
+ end
99
+ end
100
+
101
+
102
+ describe '-foo -bar baz' do
103
+ it 'returns documents not including foo and bar, but baz' do
104
+ subject.none? { |d| d.tags.include?('foo') }.must_equal true
105
+ subject.none? { |d| d.tags.include?('bar') }.must_equal true
106
+ subject.all? { |d| d.tags.include?('baz') }.must_equal true
107
+ end
108
+ end
109
+
110
+
111
+ describe '-foo -bar -baz' do
112
+ it 'returns documents not including foo, bar and baz' do
113
+ subject.none? { |d| d.tags.include?('foo') }.must_equal true
114
+ subject.none? { |d| d.tags.include?('bar') }.must_equal true
115
+ subject.none? { |d| d.tags.include?('baz') }.must_equal true
116
+ end
117
+ end
118
+
119
+
120
+ after { Document.destroy_all }
121
+ end
@@ -0,0 +1,8 @@
1
+ require 'minitest/autorun'
2
+ require 'minitest/pride'
3
+ require 'minitest/spec'
4
+
5
+ require './lib/mongoid/tags'
6
+
7
+ # Load support *.rb files in ./support
8
+ Dir[File.expand_path('../support/*.rb', __FILE__)].each { |file| require file }
@@ -0,0 +1 @@
1
+ Mongoid.load!(File.expand_path('../mongoid.yml', __FILE__), 'test')
@@ -0,0 +1,4 @@
1
+ class Document
2
+ include Mongoid::Document
3
+ include Mongoid::Tags
4
+ end
@@ -0,0 +1,8 @@
1
+ test:
2
+ sessions:
3
+ default:
4
+ database: mongoid_tags_test
5
+ hosts:
6
+ - localhost:27017
7
+ options:
8
+ consistency: :strong
data/spec/tags_spec.rb ADDED
@@ -0,0 +1,76 @@
1
+ require File.expand_path('../spec_helper', __FILE__)
2
+
3
+ describe Mongoid::Tags do
4
+ describe :selector do
5
+ let(:query) { @__name__.match(/test_\d+_(.*)/).to_a.last } # Use description as query ;)
6
+ subject { Document.selector(query) }
7
+
8
+
9
+ it 'foo' do
10
+ subject.must_equal '$in' => %w[foo]
11
+ end
12
+
13
+
14
+ it '+foo' do
15
+ subject.must_equal '$all' => %w[foo]
16
+ end
17
+
18
+
19
+ it '- foo' do
20
+ proc { subject }.must_raise Mongoid::Tags::Error
21
+ end
22
+
23
+
24
+ it '-foo' do
25
+ subject.must_equal '$nin' => %w[foo]
26
+ end
27
+
28
+
29
+ it '- foo' do
30
+ proc { subject }.must_raise Mongoid::Tags::Error
31
+ end
32
+
33
+
34
+ it '/foo' do
35
+ proc { subject }.must_raise Mongoid::Tags::Error
36
+ end
37
+
38
+
39
+ it 'foo bar' do
40
+ subject.must_equal '$in' => %w[foo bar]
41
+ end
42
+
43
+
44
+ it 'foo +bar' do
45
+ subject.must_equal '$in' => %w[foo], '$all' => %w[bar]
46
+ end
47
+
48
+
49
+ it 'foo + bar' do
50
+ proc { subject }.must_raise Mongoid::Tags::Error
51
+ end
52
+
53
+
54
+ it 'foo +bar +baz' do
55
+ subject.must_equal '$in' => %w[foo], '$all' => %w[bar baz]
56
+ end
57
+
58
+
59
+ it 'foo +bar baz' do
60
+ subject.must_equal '$in' => %w[foo baz], '$all' => %w[bar]
61
+ end
62
+
63
+
64
+ it 'foo +bar -baz' do
65
+ subject.must_equal '$in' => %w[foo], '$all' => %w[bar], '$nin' => %w[baz]
66
+ end
67
+
68
+
69
+ it 'foo foo foo' do
70
+ # Removing duplicates does not gain any
71
+ # performance improvements.
72
+ skip
73
+ subject.must_equal '$in' => %w[foo]
74
+ end
75
+ end
76
+ end
metadata ADDED
@@ -0,0 +1,108 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: mongoid-tags
3
+ version: !ruby/object:Gem::Version
4
+ prerelease:
5
+ version: 0.1.1
6
+ platform: ruby
7
+ authors:
8
+ - Mario Uher
9
+ autorequire:
10
+ bindir: bin
11
+ cert_chain: []
12
+ date: 2012-12-22 00:00:00.000000000 Z
13
+ dependencies:
14
+ - !ruby/object:Gem::Dependency
15
+ version_requirements: !ruby/object:Gem::Requirement
16
+ requirements:
17
+ - - ! '>='
18
+ - !ruby/object:Gem::Version
19
+ version: '0'
20
+ none: false
21
+ name: activesupport
22
+ type: :runtime
23
+ prerelease: false
24
+ requirement: !ruby/object:Gem::Requirement
25
+ requirements:
26
+ - - ! '>='
27
+ - !ruby/object:Gem::Version
28
+ version: '0'
29
+ none: false
30
+ - !ruby/object:Gem::Dependency
31
+ version_requirements: !ruby/object:Gem::Requirement
32
+ requirements:
33
+ - - ! '>='
34
+ - !ruby/object:Gem::Version
35
+ version: '0'
36
+ none: false
37
+ name: mongoid
38
+ type: :runtime
39
+ prerelease: false
40
+ requirement: !ruby/object:Gem::Requirement
41
+ requirements:
42
+ - - ! '>='
43
+ - !ruby/object:Gem::Version
44
+ version: '0'
45
+ none: false
46
+ - !ruby/object:Gem::Dependency
47
+ version_requirements: !ruby/object:Gem::Requirement
48
+ requirements:
49
+ - - ! '>='
50
+ - !ruby/object:Gem::Version
51
+ version: '0'
52
+ none: false
53
+ name: treetop
54
+ type: :runtime
55
+ prerelease: false
56
+ requirement: !ruby/object:Gem::Requirement
57
+ requirements:
58
+ - - ! '>='
59
+ - !ruby/object:Gem::Version
60
+ version: '0'
61
+ none: false
62
+ description: Mongoid::Tags adds a simple tagging system to your Mongoid documents,
63
+ and allows you to query them using a boolean search syntax.
64
+ email: uher.mario@gmail.com
65
+ executables: []
66
+ extensions: []
67
+ extra_rdoc_files: []
68
+ files:
69
+ - .gitignore
70
+ - CHANGELOG.md
71
+ - Gemfile
72
+ - README.md
73
+ - Rakefile
74
+ - lib/mongoid/tags.rb
75
+ - lib/mongoid/tags.tt
76
+ - lib/mongoid/tags/version.rb
77
+ - mongoid-tags.gemspec
78
+ - spec/integration_spec.rb
79
+ - spec/spec_helper.rb
80
+ - spec/support/connection.rb
81
+ - spec/support/document.rb
82
+ - spec/support/mongoid.yml
83
+ - spec/tags_spec.rb
84
+ homepage: https://github.com/haihappen/mongoid-tags
85
+ licenses: []
86
+ post_install_message:
87
+ rdoc_options: []
88
+ require_paths:
89
+ - lib
90
+ required_ruby_version: !ruby/object:Gem::Requirement
91
+ requirements:
92
+ - - ! '>='
93
+ - !ruby/object:Gem::Version
94
+ version: '0'
95
+ none: false
96
+ required_rubygems_version: !ruby/object:Gem::Requirement
97
+ requirements:
98
+ - - ! '>='
99
+ - !ruby/object:Gem::Version
100
+ version: '0'
101
+ none: false
102
+ requirements: []
103
+ rubyforge_project:
104
+ rubygems_version: 1.8.23
105
+ signing_key:
106
+ specification_version: 3
107
+ summary: Simple tagging system with boolean search.
108
+ test_files: []