minidusen 0.7.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.
@@ -0,0 +1,9 @@
1
+ source 'https://rubygems.org'
2
+
3
+ gem 'activerecord', '~>4.2.1'
4
+ gem 'rspec', '~>3.4'
5
+ gem 'pg'
6
+
7
+ gem 'byebug'
8
+
9
+ gem 'minidusen', :path => '..'
@@ -0,0 +1,63 @@
1
+ PATH
2
+ remote: ..
3
+ specs:
4
+ minidusen (0.7.0)
5
+ activerecord (>= 3.2)
6
+ activesupport (>= 3.2)
7
+ edge_rider (>= 0.2.5)
8
+
9
+ GEM
10
+ remote: https://rubygems.org/
11
+ specs:
12
+ activemodel (4.2.5)
13
+ activesupport (= 4.2.5)
14
+ builder (~> 3.1)
15
+ activerecord (4.2.5)
16
+ activemodel (= 4.2.5)
17
+ activesupport (= 4.2.5)
18
+ arel (~> 6.0)
19
+ activesupport (4.2.5)
20
+ i18n (~> 0.7)
21
+ json (~> 1.7, >= 1.7.7)
22
+ minitest (~> 5.1)
23
+ thread_safe (~> 0.3, >= 0.3.4)
24
+ tzinfo (~> 1.1)
25
+ arel (6.0.3)
26
+ builder (3.2.2)
27
+ byebug (8.2.0)
28
+ diff-lcs (1.2.5)
29
+ edge_rider (0.3.1)
30
+ activerecord
31
+ i18n (0.7.0)
32
+ json (1.8.3)
33
+ minitest (5.8.3)
34
+ pg (0.18.4)
35
+ rspec (3.4.0)
36
+ rspec-core (~> 3.4.0)
37
+ rspec-expectations (~> 3.4.0)
38
+ rspec-mocks (~> 3.4.0)
39
+ rspec-core (3.4.1)
40
+ rspec-support (~> 3.4.0)
41
+ rspec-expectations (3.4.0)
42
+ diff-lcs (>= 1.2.0, < 2.0)
43
+ rspec-support (~> 3.4.0)
44
+ rspec-mocks (3.4.1)
45
+ diff-lcs (>= 1.2.0, < 2.0)
46
+ rspec-support (~> 3.4.0)
47
+ rspec-support (3.4.1)
48
+ thread_safe (0.3.5)
49
+ tzinfo (1.2.2)
50
+ thread_safe (~> 0.1)
51
+
52
+ PLATFORMS
53
+ ruby
54
+
55
+ DEPENDENCIES
56
+ activerecord (~> 4.2.1)
57
+ byebug
58
+ minidusen!
59
+ pg
60
+ rspec (~> 3.4)
61
+
62
+ BUNDLED WITH
63
+ 1.12.5
@@ -0,0 +1,9 @@
1
+ source 'https://rubygems.org'
2
+
3
+ gem 'activerecord', '~>5.0.0'
4
+ gem 'rspec', '~>3.5'
5
+ gem 'mysql2', '~>0.4.4'
6
+
7
+ gem 'byebug'
8
+
9
+ gem 'minidusen', :path => '..'
@@ -0,0 +1,60 @@
1
+ PATH
2
+ remote: ..
3
+ specs:
4
+ minidusen (0.7.0)
5
+ activerecord (>= 3.2)
6
+ activesupport (>= 3.2)
7
+ edge_rider (>= 0.2.5)
8
+
9
+ GEM
10
+ remote: https://rubygems.org/
11
+ specs:
12
+ activemodel (5.0.0.1)
13
+ activesupport (= 5.0.0.1)
14
+ activerecord (5.0.0.1)
15
+ activemodel (= 5.0.0.1)
16
+ activesupport (= 5.0.0.1)
17
+ arel (~> 7.0)
18
+ activesupport (5.0.0.1)
19
+ concurrent-ruby (~> 1.0, >= 1.0.2)
20
+ i18n (~> 0.7)
21
+ minitest (~> 5.1)
22
+ tzinfo (~> 1.1)
23
+ arel (7.1.2)
24
+ byebug (9.0.5)
25
+ concurrent-ruby (1.0.2)
26
+ diff-lcs (1.2.5)
27
+ edge_rider (0.3.1)
28
+ activerecord
29
+ i18n (0.7.0)
30
+ minitest (5.9.0)
31
+ mysql2 (0.4.4)
32
+ rspec (3.5.0)
33
+ rspec-core (~> 3.5.0)
34
+ rspec-expectations (~> 3.5.0)
35
+ rspec-mocks (~> 3.5.0)
36
+ rspec-core (3.5.3)
37
+ rspec-support (~> 3.5.0)
38
+ rspec-expectations (3.5.0)
39
+ diff-lcs (>= 1.2.0, < 2.0)
40
+ rspec-support (~> 3.5.0)
41
+ rspec-mocks (3.5.0)
42
+ diff-lcs (>= 1.2.0, < 2.0)
43
+ rspec-support (~> 3.5.0)
44
+ rspec-support (3.5.0)
45
+ thread_safe (0.3.5)
46
+ tzinfo (1.2.2)
47
+ thread_safe (~> 0.1)
48
+
49
+ PLATFORMS
50
+ ruby
51
+
52
+ DEPENDENCIES
53
+ activerecord (~> 5.0.0)
54
+ byebug
55
+ minidusen!
56
+ mysql2 (~> 0.4.4)
57
+ rspec (~> 3.5)
58
+
59
+ BUNDLED WITH
60
+ 1.12.5
@@ -0,0 +1,9 @@
1
+ source 'https://rubygems.org'
2
+
3
+ gem 'activerecord', '~>5.0.0'
4
+ gem 'rspec', '~>3.5'
5
+ gem 'pg', '~>0.18.4'
6
+
7
+ gem 'byebug'
8
+
9
+ gem 'minidusen', :path => '..'
@@ -0,0 +1,60 @@
1
+ PATH
2
+ remote: ..
3
+ specs:
4
+ minidusen (0.7.0)
5
+ activerecord (>= 3.2)
6
+ activesupport (>= 3.2)
7
+ edge_rider (>= 0.2.5)
8
+
9
+ GEM
10
+ remote: https://rubygems.org/
11
+ specs:
12
+ activemodel (5.0.0.1)
13
+ activesupport (= 5.0.0.1)
14
+ activerecord (5.0.0.1)
15
+ activemodel (= 5.0.0.1)
16
+ activesupport (= 5.0.0.1)
17
+ arel (~> 7.0)
18
+ activesupport (5.0.0.1)
19
+ concurrent-ruby (~> 1.0, >= 1.0.2)
20
+ i18n (~> 0.7)
21
+ minitest (~> 5.1)
22
+ tzinfo (~> 1.1)
23
+ arel (7.1.2)
24
+ byebug (9.0.5)
25
+ concurrent-ruby (1.0.2)
26
+ diff-lcs (1.2.5)
27
+ edge_rider (0.3.1)
28
+ activerecord
29
+ i18n (0.7.0)
30
+ minitest (5.9.0)
31
+ pg (0.18.4)
32
+ rspec (3.5.0)
33
+ rspec-core (~> 3.5.0)
34
+ rspec-expectations (~> 3.5.0)
35
+ rspec-mocks (~> 3.5.0)
36
+ rspec-core (3.5.3)
37
+ rspec-support (~> 3.5.0)
38
+ rspec-expectations (3.5.0)
39
+ diff-lcs (>= 1.2.0, < 2.0)
40
+ rspec-support (~> 3.5.0)
41
+ rspec-mocks (3.5.0)
42
+ diff-lcs (>= 1.2.0, < 2.0)
43
+ rspec-support (~> 3.5.0)
44
+ rspec-support (3.5.0)
45
+ thread_safe (0.3.5)
46
+ tzinfo (1.2.2)
47
+ thread_safe (~> 0.1)
48
+
49
+ PLATFORMS
50
+ ruby
51
+
52
+ DEPENDENCIES
53
+ activerecord (~> 5.0.0)
54
+ byebug
55
+ minidusen!
56
+ pg (~> 0.18.4)
57
+ rspec (~> 3.5)
58
+
59
+ BUNDLED WITH
60
+ 1.12.5
data/lib/minidusen.rb ADDED
@@ -0,0 +1,12 @@
1
+ require 'active_support'
2
+ require 'active_record'
3
+ require 'edge_rider'
4
+
5
+ require 'minidusen/version'
6
+ require 'minidusen/util'
7
+ require 'minidusen/token'
8
+ require 'minidusen/parser'
9
+ require 'minidusen/query'
10
+ require 'minidusen/syntax'
11
+ require 'minidusen/filter'
12
+ require 'minidusen/active_record_ext'
@@ -0,0 +1,39 @@
1
+ module Minidusen
2
+ module ActiveRecordExtensions
3
+ module ClassMethods
4
+
5
+ def where_like(conditions, options = {})
6
+ scope = scoped
7
+
8
+ ilike_operator = Util.ilike_operator(scope)
9
+
10
+ if options[:negate]
11
+ match_operator = "NOT #{ilike_operator}"
12
+ join_operator = 'AND'
13
+ else
14
+ match_operator = ilike_operator
15
+ join_operator = 'OR'
16
+ end
17
+
18
+ conditions.each do |field_or_fields, query|
19
+ fields = Array(field_or_fields).collect do |field|
20
+ Util.qualify_column_name(scope, field)
21
+ end
22
+ Array.wrap(query).each do |phrase|
23
+ phrase_with_placeholders = fields.collect { |field|
24
+ "#{field} #{match_operator} ?"
25
+ }.join(" #{join_operator} ")
26
+ like_expression = Minidusen::Util.like_expression(phrase)
27
+ bindings = [like_expression] * fields.size
28
+ conditions = [ phrase_with_placeholders, *bindings ]
29
+ scope = scope.where(conditions)
30
+ end
31
+ end
32
+ scope
33
+ end
34
+
35
+ end
36
+ end
37
+ end
38
+
39
+ ActiveRecord::Base.send(:extend, Minidusen::ActiveRecordExtensions::ClassMethods)
@@ -0,0 +1,31 @@
1
+ module Minidusen
2
+ module Filter
3
+ module ClassMethods
4
+
5
+ private
6
+
7
+ attr_accessor :minidusen_syntax
8
+
9
+ def filter(field, &block)
10
+ minidusen_syntax.learn_field(field, &block)
11
+ end
12
+
13
+ end
14
+
15
+ def self.included(base)
16
+ base.extend(ClassMethods)
17
+ base.send(:minidusen_syntax=, Syntax.new)
18
+ end
19
+
20
+ def filter(scope, query)
21
+ minidusen_syntax.search(scope, query)
22
+ end
23
+
24
+ private
25
+
26
+ def minidusen_syntax
27
+ self.class.send(:minidusen_syntax)
28
+ end
29
+
30
+ end
31
+ end
@@ -0,0 +1,35 @@
1
+ module Minidusen
2
+ class Parser
3
+
4
+ TEXT_QUERY = /(?:(\-)?"([^"]+)"|(\-)?([\S]+))/
5
+ FIELD_QUERY = /(\-)?(\w+)\:#{TEXT_QUERY}/
6
+
7
+ def self.parse(query_string)
8
+ query_string = query_string.dup # we are going to delete substrings in-place
9
+ query = Query.new
10
+ extract_field_query_tokens(query_string, query)
11
+ extract_text_query_tokens(query_string, query)
12
+ query
13
+ end
14
+
15
+ def self.extract_text_query_tokens(query_string, query)
16
+ while query_string.sub!(TEXT_QUERY, '')
17
+ value = "#{$2}#{$4}"
18
+ exclude = "#{$1}#{$3}" == "-"
19
+ options = { :field => 'text', :value => value, :exclude => exclude }
20
+ query << Token.new(options)
21
+ end
22
+ end
23
+
24
+ def self.extract_field_query_tokens(query_string, query)
25
+ while query_string.sub!(FIELD_QUERY, '')
26
+ field = $2
27
+ value = "#{$4}#{$6}"
28
+ exclude = "#{$1}" == "-"
29
+ options = { :field => field, :value => value, :exclude => exclude }
30
+ query << Token.new(options)
31
+ end
32
+ end
33
+
34
+ end
35
+ end
@@ -0,0 +1,54 @@
1
+ module Minidusen
2
+ class Query
3
+
4
+ include Enumerable
5
+
6
+ attr_reader :tokens
7
+
8
+ def initialize(initial_tokens = [])
9
+ @tokens = initial_tokens
10
+ end
11
+
12
+ def <<(token)
13
+ tokens << token
14
+ end
15
+
16
+ def [](index)
17
+ tokens[index]
18
+ end
19
+
20
+ def to_s
21
+ collect(&:to_s).join(" + ")
22
+ end
23
+
24
+ def each(&block)
25
+ tokens.each(&block)
26
+ end
27
+
28
+ def condensed
29
+ include_texts = include.select(&:text?).collect(&:value)
30
+ exclude_texts = exclude.select(&:text?).collect(&:value)
31
+ field_tokens = tokens.reject(&:text?)
32
+
33
+ condensed_tokens = field_tokens
34
+ if include_texts.present?
35
+ options = { :field => 'text', :value => include_texts, :exclude => false }
36
+ condensed_tokens << Token.new(options)
37
+ end
38
+ if exclude_texts.present?
39
+ options = { :field => 'text', :value => exclude_texts, :exclude => true }
40
+ condensed_tokens << Token.new(options)
41
+ end
42
+ self.class.new(condensed_tokens)
43
+ end
44
+
45
+ def include
46
+ self.class.new tokens.reject(&:exclude?)
47
+ end
48
+
49
+ def exclude
50
+ self.class.new tokens.select(&:exclude?)
51
+ end
52
+
53
+ end
54
+ end
@@ -0,0 +1,54 @@
1
+ module Minidusen
2
+ class Syntax
3
+
4
+ def initialize
5
+ @scopers = {}
6
+ end
7
+
8
+ def learn_field(field, &scoper)
9
+ field = field.to_s
10
+ @scopers[field] = scoper
11
+ end
12
+
13
+ def search(root_scope, query)
14
+ query = parse(query) if query.is_a?(String)
15
+ query = query.condensed
16
+ matches = apply_query(root_scope, query.include)
17
+ if query.exclude.any?
18
+ matches = append_excludes(matches, query.exclude)
19
+ end
20
+ matches
21
+ end
22
+
23
+ def fields
24
+ @scopers
25
+ end
26
+
27
+ def parse(query)
28
+ Parser.parse(query)
29
+ end
30
+
31
+ private
32
+
33
+ NONE = lambda do |scope, *args|
34
+ scope.where('1=2')
35
+ end
36
+
37
+ def apply_query(root_scope, query)
38
+ scope = root_scope
39
+ query.each do |token|
40
+ scoper = @scopers[token.field] || NONE
41
+ scope = scoper.call(scope, token.value)
42
+ end
43
+ scope
44
+ end
45
+
46
+ def append_excludes(matches, exclude_query)
47
+ excluded_records = apply_query(matches.origin_class, exclude_query)
48
+ qualified_id_field = Util.qualify_column_name(excluded_records, excluded_records.primary_key)
49
+ exclude_sql = "#{qualified_id_field} NOT IN (#{excluded_records.select(qualified_id_field).to_sql})"
50
+ matches.where(exclude_sql)
51
+ end
52
+
53
+ end
54
+ end