nql 0.0.1 → 0.0.2
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/README.md +54 -29
- data/lib/nql.rb +10 -1
- data/lib/nql/grammar.rb +6 -6
- data/lib/nql/grammar.treetop +6 -6
- data/lib/nql/version.rb +1 -1
- data/spec/ransack_spec.rb +60 -60
- data/spec/spec_helper.rb +7 -7
- data/spec/sql_spec.rb +24 -0
- metadata +14 -14
data/README.md
CHANGED
@@ -1,29 +1,54 @@
|
|
1
|
-
# NQL
|
2
|
-
|
3
|
-
Natural Query Language built on top of ActiveRecord and Ransack
|
4
|
-
|
5
|
-
## Installation
|
6
|
-
|
7
|
-
Add this line to your application's Gemfile:
|
8
|
-
|
9
|
-
gem 'nql'
|
10
|
-
|
11
|
-
And then execute:
|
12
|
-
|
13
|
-
$ bundle
|
14
|
-
|
15
|
-
Or install it yourself as:
|
16
|
-
|
17
|
-
$ gem install nql
|
18
|
-
|
19
|
-
##
|
20
|
-
|
21
|
-
|
22
|
-
|
23
|
-
|
24
|
-
|
25
|
-
|
26
|
-
|
27
|
-
|
28
|
-
|
29
|
-
|
1
|
+
# NQL
|
2
|
+
|
3
|
+
Natural Query Language built on top of ActiveRecord and Ransack
|
4
|
+
|
5
|
+
## Installation
|
6
|
+
|
7
|
+
Add this line to your application's Gemfile:
|
8
|
+
|
9
|
+
gem 'nql'
|
10
|
+
|
11
|
+
And then execute:
|
12
|
+
|
13
|
+
$ bundle
|
14
|
+
|
15
|
+
Or install it yourself as:
|
16
|
+
|
17
|
+
$ gem install nql
|
18
|
+
|
19
|
+
## Supported comparators
|
20
|
+
|
21
|
+
----------------------------------
|
22
|
+
| Symbol | Description |
|
23
|
+
----------------------------------
|
24
|
+
| % | Contains |
|
25
|
+
| = | Equals |
|
26
|
+
| != | Not equals |
|
27
|
+
| > | Grater than |
|
28
|
+
| >= | Grater or equals than |
|
29
|
+
| < | Less than |
|
30
|
+
| <= | Less or equals than |
|
31
|
+
----------------------------------
|
32
|
+
|
33
|
+
|
34
|
+
## Usage
|
35
|
+
|
36
|
+
Converts from natural language to query expression
|
37
|
+
|
38
|
+
q = '(name % arg | name % br) & region = south'
|
39
|
+
Country.search(NQL.to_ransack(q)).result.to_sql
|
40
|
+
=> "SELECT coutries.* FROM countries WHERE (countries.name LIKE '%arg%' OR countries.name LIKE '%br%') AND region = 'south'"
|
41
|
+
|
42
|
+
### Joins support
|
43
|
+
|
44
|
+
q = 'cities.name % buenos'
|
45
|
+
Country.search(NQL.to_ransack(q)).result.to_sql
|
46
|
+
=> "SELECT countries.* FROM countries LEFT OUTER JOIN cities ON countries.id = cities.country_id WHERE cities.name LIKE '%buenos%'"
|
47
|
+
|
48
|
+
## Contributing
|
49
|
+
|
50
|
+
1. Fork it
|
51
|
+
2. Create your feature branch (`git checkout -b my-new-feature`)
|
52
|
+
3. Commit your changes (`git commit -am 'Added some feature'`)
|
53
|
+
4. Push to the branch (`git push origin my-new-feature`)
|
54
|
+
5. Create new Pull Request
|
data/lib/nql.rb
CHANGED
@@ -9,7 +9,16 @@ require 'nql/grammar'
|
|
9
9
|
module NQL
|
10
10
|
|
11
11
|
def self.to_ransack(query)
|
12
|
-
|
12
|
+
return nil if query.nil? || query.strip.empty?
|
13
|
+
expression = SyntaxParser.new.parse(query)
|
14
|
+
return invalid_condition unless expression
|
15
|
+
expression.to_ransack
|
16
|
+
end
|
17
|
+
|
18
|
+
private
|
19
|
+
|
20
|
+
def self.invalid_condition
|
21
|
+
{c: [{a: {'0' => {name: 'id'}}, p: 'eq', v: {'0' => {value: '0'}}}]}
|
13
22
|
end
|
14
23
|
|
15
24
|
end
|
data/lib/nql/grammar.rb
CHANGED
@@ -63,14 +63,14 @@ module NQL
|
|
63
63
|
|
64
64
|
module Boolean1
|
65
65
|
def to_ransack
|
66
|
-
group = {
|
66
|
+
group = {g: [{m: coordinator.to_ransack}]}
|
67
67
|
|
68
68
|
[left, right].each do |side|
|
69
69
|
if side.is_node?(:boolean)
|
70
|
-
group[
|
70
|
+
group[:g][0].merge! side.to_ransack
|
71
71
|
else
|
72
|
-
group[
|
73
|
-
group[
|
72
|
+
group[:g][0][:c] ||= []
|
73
|
+
group[:g][0][:c] << side.to_ransack
|
74
74
|
end
|
75
75
|
end
|
76
76
|
|
@@ -330,8 +330,8 @@ module NQL
|
|
330
330
|
|
331
331
|
module Comparison1
|
332
332
|
def to_ransack
|
333
|
-
hash = {
|
334
|
-
hash = {
|
333
|
+
hash = {a: {'0' => {name: self.variable.text_value.gsub('.', '_')}}, p: self.comparator.to_ransack, v: {'0' => {value: self.value.text_value}}}
|
334
|
+
hash = {c: [hash]} if !parent || !parent.parent || text_value == parent.parent.text_value
|
335
335
|
hash
|
336
336
|
end
|
337
337
|
|
data/lib/nql/grammar.treetop
CHANGED
@@ -8,14 +8,14 @@ module NQL
|
|
8
8
|
rule boolean
|
9
9
|
left:primary space coordinator:coordinator space right:expression {
|
10
10
|
def to_ransack
|
11
|
-
group = {
|
11
|
+
group = {g: [{m: coordinator.to_ransack}]}
|
12
12
|
|
13
13
|
[left, right].each do |side|
|
14
14
|
if side.is_node?(:boolean)
|
15
|
-
group[
|
15
|
+
group[:g][0].merge! side.to_ransack
|
16
16
|
else
|
17
|
-
group[
|
18
|
-
group[
|
17
|
+
group[:g][0][:c] ||= []
|
18
|
+
group[:g][0][:c] << side.to_ransack
|
19
19
|
end
|
20
20
|
end
|
21
21
|
|
@@ -56,8 +56,8 @@ module NQL
|
|
56
56
|
rule comparison
|
57
57
|
variable:alphanumeric space comparator:comparator space value:text {
|
58
58
|
def to_ransack
|
59
|
-
hash = {
|
60
|
-
hash = {
|
59
|
+
hash = {a: {'0' => {name: self.variable.text_value.gsub('.', '_')}}, p: self.comparator.to_ransack, v: {'0' => {value: self.value.text_value}}}
|
60
|
+
hash = {c: [hash]} if !parent || !parent.parent || text_value == parent.parent.text_value
|
61
61
|
hash
|
62
62
|
end
|
63
63
|
|
data/lib/nql/version.rb
CHANGED
data/spec/ransack_spec.rb
CHANGED
@@ -9,65 +9,65 @@ describe 'Ransack Query' do
|
|
9
9
|
it 'Equals' do
|
10
10
|
q = parser.parse('id = 1234').to_ransack
|
11
11
|
|
12
|
-
q[
|
13
|
-
q[
|
14
|
-
q[
|
12
|
+
q[:c][0].should have_attribute 'id'
|
13
|
+
q[:c][0].should have_predicate 'eq'
|
14
|
+
q[:c][0].should have_value '1234'
|
15
15
|
end
|
16
16
|
|
17
17
|
it 'Not equals' do
|
18
18
|
q = parser.parse('id != 1234').to_ransack
|
19
19
|
|
20
|
-
q[
|
21
|
-
q[
|
22
|
-
q[
|
20
|
+
q[:c][0].should have_attribute 'id'
|
21
|
+
q[:c][0].should have_predicate 'not_eq'
|
22
|
+
q[:c][0].should have_value '1234'
|
23
23
|
end
|
24
24
|
|
25
25
|
it 'Greater than' do
|
26
26
|
q = parser.parse('id > 1234').to_ransack
|
27
27
|
|
28
|
-
q[
|
29
|
-
q[
|
30
|
-
q[
|
28
|
+
q[:c][0].should have_attribute 'id'
|
29
|
+
q[:c][0].should have_predicate 'gt'
|
30
|
+
q[:c][0].should have_value '1234'
|
31
31
|
end
|
32
32
|
|
33
33
|
it 'Greater or equals than' do
|
34
34
|
q = parser.parse('id >= 1234').to_ransack
|
35
35
|
|
36
|
-
q[
|
37
|
-
q[
|
38
|
-
q[
|
36
|
+
q[:c][0].should have_attribute 'id'
|
37
|
+
q[:c][0].should have_predicate 'gteq'
|
38
|
+
q[:c][0].should have_value '1234'
|
39
39
|
end
|
40
40
|
|
41
41
|
it 'Less than' do
|
42
42
|
q = parser.parse('id < 1234').to_ransack
|
43
43
|
|
44
|
-
q[
|
45
|
-
q[
|
46
|
-
q[
|
44
|
+
q[:c][0].should have_attribute 'id'
|
45
|
+
q[:c][0].should have_predicate 'lt'
|
46
|
+
q[:c][0].should have_value '1234'
|
47
47
|
end
|
48
48
|
|
49
49
|
it 'Less or equals than' do
|
50
50
|
q = parser.parse('id <= 1234').to_ransack
|
51
51
|
|
52
|
-
q[
|
53
|
-
q[
|
54
|
-
q[
|
52
|
+
q[:c][0].should have_attribute 'id'
|
53
|
+
q[:c][0].should have_predicate 'lteq'
|
54
|
+
q[:c][0].should have_value '1234'
|
55
55
|
end
|
56
56
|
|
57
57
|
it 'Contains' do
|
58
58
|
q = parser.parse('id % 1234').to_ransack
|
59
59
|
|
60
|
-
q[
|
61
|
-
q[
|
62
|
-
q[
|
60
|
+
q[:c][0].should have_attribute 'id'
|
61
|
+
q[:c][0].should have_predicate 'cont'
|
62
|
+
q[:c][0].should have_value '1234'
|
63
63
|
end
|
64
64
|
|
65
65
|
it 'Model references' do
|
66
66
|
q = parser.parse('models.id = 1234').to_ransack
|
67
67
|
|
68
|
-
q[
|
69
|
-
q[
|
70
|
-
q[
|
68
|
+
q[:c][0].should have_attribute 'models_id'
|
69
|
+
q[:c][0].should have_predicate 'eq'
|
70
|
+
q[:c][0].should have_value '1234'
|
71
71
|
end
|
72
72
|
|
73
73
|
end
|
@@ -77,57 +77,57 @@ describe 'Ransack Query' do
|
|
77
77
|
it 'And' do
|
78
78
|
q = parser.parse('id > 1234 & name = abcd').to_ransack
|
79
79
|
|
80
|
-
q[
|
81
|
-
q[
|
82
|
-
q[
|
83
|
-
q[
|
84
|
-
q[
|
85
|
-
q[
|
86
|
-
q[
|
80
|
+
q[:g][0][:m].should eq 'and'
|
81
|
+
q[:g][0][:c][0].should have_attribute 'id'
|
82
|
+
q[:g][0][:c][0].should have_predicate 'gt'
|
83
|
+
q[:g][0][:c][0].should have_value '1234'
|
84
|
+
q[:g][0][:c][1].should have_attribute 'name'
|
85
|
+
q[:g][0][:c][1].should have_predicate 'eq'
|
86
|
+
q[:g][0][:c][1].should have_value 'abcd'
|
87
87
|
end
|
88
88
|
|
89
89
|
it 'Or' do
|
90
90
|
q = parser.parse('id < 1234 | name % abcd').to_ransack
|
91
91
|
|
92
|
-
q[
|
93
|
-
q[
|
94
|
-
q[
|
95
|
-
q[
|
96
|
-
q[
|
97
|
-
q[
|
98
|
-
q[
|
92
|
+
q[:g][0][:m].should eq 'or'
|
93
|
+
q[:g][0][:c][0].should have_attribute 'id'
|
94
|
+
q[:g][0][:c][0].should have_predicate 'lt'
|
95
|
+
q[:g][0][:c][0].should have_value '1234'
|
96
|
+
q[:g][0][:c][1].should have_attribute 'name'
|
97
|
+
q[:g][0][:c][1].should have_predicate 'cont'
|
98
|
+
q[:g][0][:c][1].should have_value 'abcd'
|
99
99
|
end
|
100
100
|
|
101
101
|
it 'And then Or' do
|
102
102
|
q = parser.parse('id > 1234 & name = abcd | name % efgh').to_ransack
|
103
103
|
|
104
|
-
q[
|
105
|
-
q[
|
106
|
-
q[
|
107
|
-
q[
|
108
|
-
q[
|
109
|
-
q[
|
110
|
-
q[
|
111
|
-
q[
|
112
|
-
q[
|
113
|
-
q[
|
114
|
-
q[
|
104
|
+
q[:g][0][:m].should eq 'and'
|
105
|
+
q[:g][0][:c][0].should have_attribute 'id'
|
106
|
+
q[:g][0][:c][0].should have_predicate 'gt'
|
107
|
+
q[:g][0][:c][0].should have_value '1234'
|
108
|
+
q[:g][0][:g][0][:m].should eq 'or'
|
109
|
+
q[:g][0][:g][0][:c][0].should have_attribute 'name'
|
110
|
+
q[:g][0][:g][0][:c][0].should have_predicate 'eq'
|
111
|
+
q[:g][0][:g][0][:c][0].should have_value 'abcd'
|
112
|
+
q[:g][0][:g][0][:c][1].should have_attribute 'name'
|
113
|
+
q[:g][0][:g][0][:c][1].should have_predicate 'cont'
|
114
|
+
q[:g][0][:g][0][:c][1].should have_value 'efgh'
|
115
115
|
end
|
116
116
|
|
117
117
|
it 'With parentheses' do
|
118
118
|
q = parser.parse('(id > 1234 & name = abcd) | name % efgh').to_ransack
|
119
119
|
|
120
|
-
q[
|
121
|
-
q[
|
122
|
-
q[
|
123
|
-
q[
|
124
|
-
q[
|
125
|
-
q[
|
126
|
-
q[
|
127
|
-
q[
|
128
|
-
q[
|
129
|
-
q[
|
130
|
-
q[
|
120
|
+
q[:g][0][:g][0][:m].should eq 'and'
|
121
|
+
q[:g][0][:g][0][:c][0].should have_attribute 'id'
|
122
|
+
q[:g][0][:g][0][:c][0].should have_predicate 'gt'
|
123
|
+
q[:g][0][:g][0][:c][0].should have_value '1234'
|
124
|
+
q[:g][0][:g][0][:c][1].should have_attribute 'name'
|
125
|
+
q[:g][0][:g][0][:c][1].should have_predicate 'eq'
|
126
|
+
q[:g][0][:g][0][:c][1].should have_value 'abcd'
|
127
|
+
q[:g][0][:m].should eq 'or'
|
128
|
+
q[:g][0][:c][0].should have_attribute 'name'
|
129
|
+
q[:g][0][:c][0].should have_predicate 'cont'
|
130
|
+
q[:g][0][:c][0].should have_value 'efgh'
|
131
131
|
end
|
132
132
|
|
133
133
|
end
|
data/spec/spec_helper.rb
CHANGED
@@ -6,37 +6,37 @@ ActiveRecord::Migrator.migrations_path = "#{File.dirname(__FILE__)}/migrations"
|
|
6
6
|
|
7
7
|
RSpec::Matchers.define :have_attribute do |expected|
|
8
8
|
match do |actual|
|
9
|
-
actual[
|
9
|
+
actual[:a]['0'][:name] == expected
|
10
10
|
end
|
11
11
|
|
12
12
|
failure_message_for_should do |actual|
|
13
|
-
"expected: #{actual[
|
13
|
+
"expected: #{actual[:a]['0'][:name]}\n got: #{expected}"
|
14
14
|
end
|
15
15
|
end
|
16
16
|
|
17
17
|
RSpec::Matchers.define :have_predicate do |expected|
|
18
18
|
match do |actual|
|
19
|
-
actual[
|
19
|
+
actual[:p] == expected
|
20
20
|
end
|
21
21
|
|
22
22
|
failure_message_for_should do |actual|
|
23
|
-
"expected: #{actual[
|
23
|
+
"expected: #{actual[:p]}\n got: #{expected}"
|
24
24
|
end
|
25
25
|
end
|
26
26
|
|
27
27
|
RSpec::Matchers.define :have_value do |expected|
|
28
28
|
match do |actual|
|
29
|
-
actual[
|
29
|
+
actual[:v]['0'][:value] == expected
|
30
30
|
end
|
31
31
|
|
32
32
|
failure_message_for_should do |actual|
|
33
|
-
"expected: #{actual[
|
33
|
+
"expected: #{actual[:v]['0'][:value]}\n got: #{expected}"
|
34
34
|
end
|
35
35
|
end
|
36
36
|
|
37
37
|
RSpec::Matchers.define :produce_sql do |expected|
|
38
38
|
match do |actual|
|
39
|
-
actual.to_sql == expected
|
39
|
+
actual.to_sql.strip == expected
|
40
40
|
end
|
41
41
|
|
42
42
|
failure_message_for_should do |actual|
|
data/spec/sql_spec.rb
CHANGED
@@ -90,4 +90,28 @@ describe 'SQL generation' do
|
|
90
90
|
|
91
91
|
end
|
92
92
|
|
93
|
+
context 'Invalid queries' do
|
94
|
+
|
95
|
+
it 'Nil' do
|
96
|
+
q = nil
|
97
|
+
Country.search(NQL.to_ransack(q)).result.should produce_sql "SELECT \"countries\".* FROM \"countries\""
|
98
|
+
end
|
99
|
+
|
100
|
+
it 'Empty' do
|
101
|
+
q = ''
|
102
|
+
Country.search(NQL.to_ransack(q)).result.should produce_sql "SELECT \"countries\".* FROM \"countries\""
|
103
|
+
end
|
104
|
+
|
105
|
+
it 'Empty with spaces' do
|
106
|
+
q = ' '
|
107
|
+
Country.search(NQL.to_ransack(q)).result.should produce_sql "SELECT \"countries\".* FROM \"countries\""
|
108
|
+
end
|
109
|
+
|
110
|
+
it 'Partial expression' do
|
111
|
+
q = 'id ='
|
112
|
+
Country.search(NQL.to_ransack(q)).result.should produce_sql "SELECT \"countries\".* FROM \"countries\" WHERE \"countries\".\"id\" = 0"
|
113
|
+
end
|
114
|
+
|
115
|
+
end
|
116
|
+
|
93
117
|
end
|
metadata
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: nql
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 0.0.
|
4
|
+
version: 0.0.2
|
5
5
|
prerelease:
|
6
6
|
platform: ruby
|
7
7
|
authors:
|
@@ -9,11 +9,11 @@ authors:
|
|
9
9
|
autorequire:
|
10
10
|
bindir: bin
|
11
11
|
cert_chain: []
|
12
|
-
date: 2012-11-
|
12
|
+
date: 2012-11-09 00:00:00.000000000 Z
|
13
13
|
dependencies:
|
14
14
|
- !ruby/object:Gem::Dependency
|
15
15
|
name: treetop
|
16
|
-
requirement: &
|
16
|
+
requirement: &28497180 !ruby/object:Gem::Requirement
|
17
17
|
none: false
|
18
18
|
requirements:
|
19
19
|
- - ! '>='
|
@@ -21,10 +21,10 @@ dependencies:
|
|
21
21
|
version: '0'
|
22
22
|
type: :runtime
|
23
23
|
prerelease: false
|
24
|
-
version_requirements: *
|
24
|
+
version_requirements: *28497180
|
25
25
|
- !ruby/object:Gem::Dependency
|
26
26
|
name: activerecord
|
27
|
-
requirement: &
|
27
|
+
requirement: &28496880 !ruby/object:Gem::Requirement
|
28
28
|
none: false
|
29
29
|
requirements:
|
30
30
|
- - ! '>='
|
@@ -32,10 +32,10 @@ dependencies:
|
|
32
32
|
version: 3.2.0
|
33
33
|
type: :runtime
|
34
34
|
prerelease: false
|
35
|
-
version_requirements: *
|
35
|
+
version_requirements: *28496880
|
36
36
|
- !ruby/object:Gem::Dependency
|
37
37
|
name: activesupport
|
38
|
-
requirement: &
|
38
|
+
requirement: &28496568 !ruby/object:Gem::Requirement
|
39
39
|
none: false
|
40
40
|
requirements:
|
41
41
|
- - ! '>='
|
@@ -43,10 +43,10 @@ dependencies:
|
|
43
43
|
version: 3.2.0
|
44
44
|
type: :runtime
|
45
45
|
prerelease: false
|
46
|
-
version_requirements: *
|
46
|
+
version_requirements: *28496568
|
47
47
|
- !ruby/object:Gem::Dependency
|
48
48
|
name: ransack
|
49
|
-
requirement: &
|
49
|
+
requirement: &28496340 !ruby/object:Gem::Requirement
|
50
50
|
none: false
|
51
51
|
requirements:
|
52
52
|
- - ! '>='
|
@@ -54,10 +54,10 @@ dependencies:
|
|
54
54
|
version: '0'
|
55
55
|
type: :runtime
|
56
56
|
prerelease: false
|
57
|
-
version_requirements: *
|
57
|
+
version_requirements: *28496340
|
58
58
|
- !ruby/object:Gem::Dependency
|
59
59
|
name: sqlite3
|
60
|
-
requirement: &
|
60
|
+
requirement: &28496064 !ruby/object:Gem::Requirement
|
61
61
|
none: false
|
62
62
|
requirements:
|
63
63
|
- - ! '>='
|
@@ -65,10 +65,10 @@ dependencies:
|
|
65
65
|
version: '0'
|
66
66
|
type: :development
|
67
67
|
prerelease: false
|
68
|
-
version_requirements: *
|
68
|
+
version_requirements: *28496064
|
69
69
|
- !ruby/object:Gem::Dependency
|
70
70
|
name: rspec
|
71
|
-
requirement: &
|
71
|
+
requirement: &28495812 !ruby/object:Gem::Requirement
|
72
72
|
none: false
|
73
73
|
requirements:
|
74
74
|
- - ! '>='
|
@@ -76,7 +76,7 @@ dependencies:
|
|
76
76
|
version: '0'
|
77
77
|
type: :development
|
78
78
|
prerelease: false
|
79
|
-
version_requirements: *
|
79
|
+
version_requirements: *28495812
|
80
80
|
description: Natural Query Language built on top of ActiveRecord and Ransack
|
81
81
|
email:
|
82
82
|
- gabynaiman@gmail.com
|