minidusen 0.7.0

Sign up to get free protection for your applications and to get access to all the features.
@@ -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