smql 0.0.2 → 0.0.3
Sign up to get free protection for your applications and to get access to all the features.
- data/VERSION +1 -1
- data/lib/smql_to_ar/condition_types.rb +76 -23
- data/lib/smql_to_ar/query_builder.rb +58 -21
- data/lib/smql_to_ar.rb +43 -5
- metadata +16 -3
data/VERSION
CHANGED
@@ -1 +1 @@
|
|
1
|
-
0.0.
|
1
|
+
0.0.3
|
@@ -25,6 +25,8 @@ class SmqlToAR
|
|
25
25
|
# Nimmt eine Klasse ein Objekt an, so soll diese Klasse instanziert werden.
|
26
26
|
# Alles weitere siehe Condition.
|
27
27
|
module ConditionTypes
|
28
|
+
extend SmqlToAR::Assertion
|
29
|
+
|
28
30
|
class <<self
|
29
31
|
# Ex: 'givenname|surname|nick' => [:givenname, :surname, :nick]
|
30
32
|
def split_keys k
|
@@ -40,13 +42,13 @@ class SmqlToAR
|
|
40
42
|
next if :Condition == c
|
41
43
|
c = const_get c
|
42
44
|
next if Condition === c
|
43
|
-
|
45
|
+
raise_unless colop =~ /^(?:\d*:)?(.*?)(\W*)$/, UnexpectedColOpError.new( model, colop, val)
|
44
46
|
col, op = $1, $2
|
45
47
|
col = split_keys( col).collect {|c| Column.new model, c }
|
46
48
|
r = c.try_parse model, col, op, val
|
47
49
|
break if r
|
48
50
|
end
|
49
|
-
|
51
|
+
raise_unless r, UnexpectedError.new( model, colop, val)
|
50
52
|
r
|
51
53
|
end
|
52
54
|
|
@@ -72,6 +74,8 @@ class SmqlToAR
|
|
72
74
|
end
|
73
75
|
|
74
76
|
class Condition
|
77
|
+
include SmqlToAR::Assertion
|
78
|
+
extend SmqlToAR::Assertion
|
75
79
|
attr_reader :value, :cols
|
76
80
|
Operator = nil
|
77
81
|
Expected = []
|
@@ -81,7 +85,7 @@ class SmqlToAR
|
|
81
85
|
# Passt das Object, die Klasse instanzieren.
|
82
86
|
def self.try_parse model, cols, op, val
|
83
87
|
#p :self => name, :try_parse => op, :cols => cols, :with => self::Operator, :value => val, :expected => self::Expected, :model => model.name
|
84
|
-
new model, cols, val if self::Operator === op and self::Expected.any?
|
88
|
+
new model, cols, val if self::Operator === op and self::Expected.any?( &it === val)
|
85
89
|
end
|
86
90
|
|
87
91
|
def initialize model, cols, val
|
@@ -103,14 +107,14 @@ class SmqlToAR
|
|
103
107
|
# Gibt es eine Spalte diesen Namens?
|
104
108
|
# Oder: Gibt es eine Relation diesen Namens? (Hier nicht der Fall)
|
105
109
|
def verify_column col
|
106
|
-
|
110
|
+
raise_unless col.exist_in?, NonExistingColumnError.new( %w[Column], col)
|
107
111
|
end
|
108
112
|
|
109
113
|
# Modelle koennen Spalten/Relationen verbieten mit Model#smql_protected.
|
110
114
|
# Dieses muss ein Object mit #include?( name_als_string) zurueckliefern,
|
111
115
|
# welches true fuer verboten und false fuer, erlaubt steht.
|
112
116
|
def verify_allowed col
|
113
|
-
|
117
|
+
raise_if col.protected?, ProtectedColumnError.new( col)
|
114
118
|
end
|
115
119
|
|
116
120
|
# Erstelle alle noetigen Klauseln. builder nimmt diese entgegen,
|
@@ -196,31 +200,64 @@ class SmqlToAR
|
|
196
200
|
super( *pars)
|
197
201
|
cols = {}
|
198
202
|
@cols.each do |col|
|
199
|
-
col_model =
|
200
|
-
#p col_model: col_model.to_s, value: @value
|
203
|
+
col_model = col.relation
|
201
204
|
cols[col] = [col_model] + ConditionTypes.try_parse( col_model, @value)
|
202
205
|
end
|
203
206
|
@cols = cols
|
204
207
|
end
|
205
208
|
|
206
209
|
def verify_column col
|
207
|
-
|
208
|
-
#p refl: refl, model: @model.name, col: col, :reflections => @model.reflections.keys
|
209
|
-
raise NonExistingRelationError.new( %w[Relation], col) unless refl
|
210
|
+
raise_unless col.relation, NonExistingRelationError.new( %w[Relation], col)
|
210
211
|
end
|
211
212
|
|
212
213
|
def build builder, table
|
213
214
|
@cols.each do |col, sub|
|
214
215
|
t = table + col.path + [col.col]
|
215
|
-
#p sub: sub
|
216
|
-
p col: col, joins: col.joins
|
217
216
|
col.joins.each {|j, m| builder.join table+j, m }
|
218
|
-
builder.join t,
|
219
|
-
sub[1..-1].each
|
217
|
+
builder.join t, sub[0]
|
218
|
+
sub[1..-1].each &it.build( builder, t)
|
219
|
+
end
|
220
|
+
self
|
221
|
+
end
|
222
|
+
end
|
223
|
+
|
224
|
+
# Takes to Queries.
|
225
|
+
# First Query will be a Subquery, second a regular query.
|
226
|
+
# Example:
|
227
|
+
# Person.smql 'sub.articles:' => [{'limit:' => 1, 'order:': 'updated_at desc'}, {'content~' => 'some text'}]
|
228
|
+
# Person must have as last Article (compared by updated_at) owned by Person a Artive which has 'some text' in content.
|
229
|
+
# The last Article needn't to have 'some text' has content, the subquery takes it anyway.
|
230
|
+
# But the second query compares to it and never to any other Article, because these are filtered by first query.
|
231
|
+
# The difference to
|
232
|
+
# Person.smql :articles => {'content~' => 'some text', 'limit:' => 1, 'order:': 'updated_at desc'}
|
233
|
+
# is, second is not allowed (limit and order must be in root) and this means something like
|
234
|
+
# "Person must have the Article owned by Person which has 'some text' in content.
|
235
|
+
# limit and order has no function in this query and this article needn't to be the last."
|
236
|
+
class SubEqualJoin < EqualJoin
|
237
|
+
Operator = '()'
|
238
|
+
Expected = [lambda {|x| x.kind_of?( Array) and (1..2).include?( x.length) and x.all?( &it.kind_of?( Hash))}]
|
239
|
+
|
240
|
+
def initialize model, cols, val
|
241
|
+
super model, cols, val[1]
|
242
|
+
# sub: model, subquery, sub(condition)
|
243
|
+
@cols.each {|col, sub| sub[ 1...1] = SmqlToAR.new( col.relation, val[0]).parse }
|
244
|
+
end
|
245
|
+
|
246
|
+
def verify_column col
|
247
|
+
raise_unless col.child?, ConColumnError.new( [:Column], col)
|
248
|
+
end
|
249
|
+
|
250
|
+
def build builder, table
|
251
|
+
@cols.each do |col, sub|
|
252
|
+
t = table+col.to_a
|
253
|
+
p t: t, sub: sub
|
254
|
+
builder.sub_join t, col, *sub[0..1]
|
255
|
+
sub[2..-1].each &it.build( builder, t)
|
220
256
|
end
|
221
257
|
self
|
222
258
|
end
|
223
259
|
end
|
260
|
+
|
224
261
|
Equal = simple_condition Condition, '=', "%s = %s", [Array, String, Numeric]
|
225
262
|
Equal2 = simple_condition Equal, '', "%s = %s", [String, Numeric]
|
226
263
|
GreaterThan = simple_condition Condition, '>', "%s > %s", [Array, Numeric]
|
@@ -236,7 +273,7 @@ class SmqlToAR
|
|
236
273
|
Expected = [nil]
|
237
274
|
|
238
275
|
def verify_column col
|
239
|
-
|
276
|
+
raise_unless col.exist_in? || SmqlToAR.model_of( col.last_model, col.col), NonExistingSelectableError.new( col)
|
240
277
|
end
|
241
278
|
|
242
279
|
def build builder, table
|
@@ -258,13 +295,14 @@ class SmqlToAR
|
|
258
295
|
Expected = [String, Array, Hash, Numeric, nil]
|
259
296
|
|
260
297
|
class Function
|
298
|
+
include SmqlToAR::Assertion
|
261
299
|
Name = nil
|
262
300
|
Expected = []
|
263
301
|
attr_reader :model, :func, :args
|
264
302
|
|
265
303
|
def self.try_parse model, func, args
|
266
304
|
SmqlToAR.logger.info( { try_parse: [func,args]}.inspect)
|
267
|
-
self.new model, func, args if self::Name === func and self::Expected.any?
|
305
|
+
self.new model, func, args if self::Name === func and self::Expected.any?( &it === args)
|
268
306
|
end
|
269
307
|
|
270
308
|
def initialize model, func, args
|
@@ -277,22 +315,19 @@ class SmqlToAR
|
|
277
315
|
Expected = [String, Array, Hash, nil]
|
278
316
|
|
279
317
|
def initialize model, func, args
|
280
|
-
SmqlToAR.logger.info( {args: args}.inspect)
|
281
318
|
args = case args
|
282
319
|
when String then [args]
|
283
320
|
when Array, Hash then args.to_a
|
284
321
|
when nil then nil
|
285
322
|
else raise 'Oops'
|
286
323
|
end
|
287
|
-
SmqlToAR.logger.info( {args: args}.inspect)
|
288
324
|
args.andand.collect! do |o|
|
289
325
|
o = Array.wrap o
|
290
326
|
col = Column.new model, o.first
|
291
327
|
o = 'desc' == o.last.to_s.downcase ? :DESC : :ASC
|
292
|
-
|
328
|
+
raise_unless col.exist_in?, NonExistingColumnError.new( [:Column], col)
|
293
329
|
[col, o]
|
294
330
|
end
|
295
|
-
SmqlToAR.logger.info( {args: args}.inspect)
|
296
331
|
super model, func, args
|
297
332
|
end
|
298
333
|
|
@@ -302,12 +337,32 @@ class SmqlToAR
|
|
302
337
|
col, o = o
|
303
338
|
col.joins builder, table
|
304
339
|
t = table + col.path
|
305
|
-
|
340
|
+
raise_unless 1 == t.length, RootOnlyFunctionError.new( t)
|
306
341
|
builder.order t, col.col, o
|
307
342
|
end
|
308
343
|
end
|
309
344
|
end
|
310
345
|
|
346
|
+
class Limit < Function
|
347
|
+
Name = :limit
|
348
|
+
Expected = [Fixnum]
|
349
|
+
|
350
|
+
def build builder, table
|
351
|
+
raise_unless 1 == table.length, RootOnlyFunctionError.new( table)
|
352
|
+
builder.limit @args
|
353
|
+
end
|
354
|
+
end
|
355
|
+
|
356
|
+
class Offset < Function
|
357
|
+
Name = :offset
|
358
|
+
Expected = [Fixnum]
|
359
|
+
|
360
|
+
def build builder, table
|
361
|
+
raise_unless 1 == table.length, RootOnlyFunctionError.new( table)
|
362
|
+
builder.offset @args
|
363
|
+
end
|
364
|
+
end
|
365
|
+
|
311
366
|
def self.new model, col, val
|
312
367
|
SmqlToAR.logger.info( { function: col.first.to_sym }.inspect)
|
313
368
|
r = nil
|
@@ -315,9 +370,7 @@ class SmqlToAR
|
|
315
370
|
next if [:Function, :Where, :Expected, :Operator].include? c
|
316
371
|
c = const_get c
|
317
372
|
next if Function === c or not c.respond_to?( :try_parse)
|
318
|
-
SmqlToAR.logger.info( {f: c}.inspect)
|
319
373
|
r = c.try_parse model, col.first.to_sym, val
|
320
|
-
SmqlToAR.logger.info( {r: r}.inspect)
|
321
374
|
break if r
|
322
375
|
end
|
323
376
|
r
|
@@ -22,23 +22,24 @@ class SmqlToAR
|
|
22
22
|
class Vid
|
23
23
|
attr_reader :vid
|
24
24
|
def initialize( vid) @vid = vid end
|
25
|
-
def to_s() ":
|
26
|
-
def to_sym() "
|
25
|
+
def to_s() ":smql_c#{@vid}" end
|
26
|
+
def to_sym() "smql_c#{@vid}".to_sym end
|
27
27
|
alias sym to_sym
|
28
28
|
def to_i() @vid end
|
29
29
|
end
|
30
30
|
|
31
|
-
attr_reader :table_alias, :model, :table_model, :base_table, :_where, :_select, :_wobs, :_joins
|
31
|
+
attr_reader :table_alias, :model, :table_model, :base_table, :_where, :_select, :_wobs, :_joins, :prefix, :_vid
|
32
32
|
attr_accessor :logger
|
33
33
|
|
34
|
-
def initialize model
|
34
|
+
def initialize model, prefix = nil, base_table
|
35
|
+
@prefix = "smql"
|
35
36
|
@logger = SmqlToAR.logger
|
36
37
|
@table_alias = Hash.new do |h, k|
|
37
38
|
k = Array.wrap k
|
38
|
-
h[k] = "
|
39
|
+
h[k] = "#{@prefix},#{k.join(',')}"
|
39
40
|
end
|
40
41
|
@_vid, @_where, @_wobs, @model, @quoter = 0, [], {}, model, model.connection
|
41
|
-
@base_table = [model.table_name.to_sym]
|
42
|
+
@base_table = base_table.blank? ? [model.table_name.to_sym] : Array.wrap( base_table)
|
42
43
|
@table_alias[ @base_table] = @base_table.first
|
43
44
|
t = quote_table_name @table_alias[ @base_table]
|
44
45
|
@_select, @_joins, @_joined, @_includes, @_order = ["DISTINCT #{t}.*"], "", [], [], []
|
@@ -74,32 +75,57 @@ class SmqlToAR
|
|
74
75
|
end
|
75
76
|
|
76
77
|
def build_join orig, pretable, table, prekey, key
|
77
|
-
" JOIN #{
|
78
|
+
" JOIN #{orig} AS #{quote_table_name table} ON #{column pretable, prekey} = #{column table, key} "
|
78
79
|
end
|
79
80
|
|
80
|
-
def
|
81
|
-
|
82
|
-
|
81
|
+
def sub_join table, col, model, query
|
82
|
+
pp [:sub_join, table, col. model, query]
|
83
|
+
prefix, base_table = "#{@prefix}_sub", col.col
|
84
|
+
join_ table, model, "(#{query.build( prefix, base_table).tap{|q| p :sub_join => q }.ar.to_sql})"
|
85
|
+
end
|
86
|
+
|
87
|
+
def join_ table, model, query, pretable = nil
|
88
|
+
pp [:join_, table, model, query]
|
89
|
+
pretable ||= table[0...-1]
|
83
90
|
@table_model[ table] = model
|
84
91
|
premodel = @table_model[ pretable]
|
85
92
|
t = @table_alias[ table]
|
86
93
|
pt = quote_table_name @table_alias[ table[ 0...-1]]
|
94
|
+
pp premodel: premodel, table: table
|
87
95
|
refl = premodel.reflections[table.last]
|
88
|
-
case refl
|
89
|
-
when
|
90
|
-
|
91
|
-
|
92
|
-
|
93
|
-
|
94
|
-
|
95
|
-
@
|
96
|
-
|
97
|
-
|
96
|
+
case refl
|
97
|
+
when ActiveRecord::Reflection::ThroughReflection
|
98
|
+
through = refl.through_reflection
|
99
|
+
pp refl: refl
|
100
|
+
throughtable = table[0...-1]+[through.name.to_sym]
|
101
|
+
srctable = throughtable+[refl.source_reflection.name]
|
102
|
+
@table_model[ srctable] = model
|
103
|
+
@table_alias[ table] = @table_alias[ srctable]
|
104
|
+
join_ throughtable, through.klass, quote_table_name( through.table_name)
|
105
|
+
join_ srctable, refl.klass, query, throughtable
|
106
|
+
when ActiveRecord::Reflection::AssociationReflection
|
107
|
+
case refl.macro
|
108
|
+
when :has_many
|
109
|
+
@_joins += build_join query, pretable, t, premodel.primary_key, refl.primary_key_name
|
110
|
+
when :belongs_to
|
111
|
+
@_joins += build_join query, pretable, t, refl.primary_key_name, premodel.primary_key
|
112
|
+
when :has_and_belongs_to_many
|
113
|
+
jointable = [','] + table
|
114
|
+
@_joins += build_join refl.options[:join_table], pretable, @table_alias[jointable], premodel.primary_key, refl.primary_key_name
|
115
|
+
@_joins += build_join query, jointable, t, refl.association_foreign_key, refl.association_primary_key
|
116
|
+
else raise BuilderError, "Unkown reflection macro: #{refl.macro.inspect}"
|
117
|
+
end
|
118
|
+
else raise BuilderError, "Unkown reflection type: #{refl.class.name}"
|
98
119
|
end
|
99
|
-
@_joined.push table
|
100
120
|
self
|
101
121
|
end
|
102
122
|
|
123
|
+
def join table, model
|
124
|
+
return self if @_joined.include? table # Already joined
|
125
|
+
join_ table, model, quote_table_name( model.table_name)
|
126
|
+
@_joined.push table
|
127
|
+
end
|
128
|
+
|
103
129
|
def includes table
|
104
130
|
@_includes.push table
|
105
131
|
self
|
@@ -114,6 +140,14 @@ class SmqlToAR
|
|
114
140
|
@_order.push "#{column table, col} #{:DESC == o ? :DESC : :ASC}"
|
115
141
|
end
|
116
142
|
|
143
|
+
def limit count
|
144
|
+
@_limit = count
|
145
|
+
end
|
146
|
+
|
147
|
+
def offset count
|
148
|
+
@_offset = count
|
149
|
+
end
|
150
|
+
|
117
151
|
class Dummy
|
118
152
|
def method_missing m, *a, &e
|
119
153
|
#p :dummy => m, :pars => a, :block => e
|
@@ -138,6 +172,9 @@ class SmqlToAR
|
|
138
172
|
where( where_str, @_wobs).
|
139
173
|
order( @_order.join( ', ')).
|
140
174
|
includes( incls)
|
175
|
+
@model = @model.limit @_limit if @_limit
|
176
|
+
@model = @model.offset @_offset if @_offset
|
177
|
+
@model
|
141
178
|
end
|
142
179
|
|
143
180
|
def fix_calculate
|
data/lib/smql_to_ar.rb
CHANGED
@@ -15,6 +15,18 @@
|
|
15
15
|
# along with this program. If not, see <http://www.gnu.org/licenses/>.
|
16
16
|
|
17
17
|
class SmqlToAR
|
18
|
+
module Assertion
|
19
|
+
def raise_unless cond, exception = nil, *args
|
20
|
+
cond, exception, *args = yield. cond, exception, *args if block_given?
|
21
|
+
raise exception || Exception, *args unless cond
|
22
|
+
end
|
23
|
+
|
24
|
+
def raise_if cond, exception = nil, *args
|
25
|
+
cond, exception, *args = yield. cond, exception, *args if block_given?
|
26
|
+
raise exception || Exception, *args if cond
|
27
|
+
end
|
28
|
+
end
|
29
|
+
|
18
30
|
include ActiveSupport::Benchmarkable
|
19
31
|
############################################################################r
|
20
32
|
# Exceptions
|
@@ -77,12 +89,18 @@ class SmqlToAR
|
|
77
89
|
end
|
78
90
|
end
|
79
91
|
|
80
|
-
class
|
92
|
+
class RootOnlyFunctionError < SMQLError
|
81
93
|
def initialize path
|
82
94
|
super :path => path
|
83
95
|
end
|
84
96
|
end
|
85
97
|
|
98
|
+
class ConColumnError < SMQLError
|
99
|
+
def initialize expected, got
|
100
|
+
super :expected => expected, :got => got
|
101
|
+
end
|
102
|
+
end
|
103
|
+
|
86
104
|
class BuilderError < Exception; end
|
87
105
|
|
88
106
|
#############################################################################
|
@@ -103,7 +121,7 @@ class SmqlToAR
|
|
103
121
|
def initialize model, *col
|
104
122
|
@model = model
|
105
123
|
@last_model = nil
|
106
|
-
*@path, @col = Array.wrap( col).collect
|
124
|
+
*@path, @col = Array.wrap( col).collect( &it.to_s.split( /[.\/]/)).flatten.collect( &:to_sym)
|
107
125
|
end
|
108
126
|
|
109
127
|
def last_model
|
@@ -146,6 +164,8 @@ class SmqlToAR
|
|
146
164
|
exe.call pp, model
|
147
165
|
end
|
148
166
|
end
|
167
|
+
def length() @path.length+1 end
|
168
|
+
def size() @path.size+1 end
|
149
169
|
def to_a() @path+[@col] end
|
150
170
|
def to_s() to_a.join '.' end
|
151
171
|
def to_sym() to_s.to_sym end
|
@@ -153,6 +173,7 @@ class SmqlToAR
|
|
153
173
|
def inspect() "#<Column: #{model} #{to_s}>" end
|
154
174
|
def relation() SmqlToAR.model_of last_model, @col end
|
155
175
|
def allowed?() ! self.protected? end
|
176
|
+
def child?() @path.empty? and !!relation end
|
156
177
|
end
|
157
178
|
|
158
179
|
attr_reader :model, :query, :conditions, :builder, :order
|
@@ -179,6 +200,23 @@ class SmqlToAR
|
|
179
200
|
#p model: @model, query: @query
|
180
201
|
end
|
181
202
|
|
203
|
+
def self.models models
|
204
|
+
models = Array.wrap models
|
205
|
+
r = Hash.new {|h,k| h[k] = {} }
|
206
|
+
while model = models.tap( &:uniq!).pop
|
207
|
+
refls = model.respond_to?( :reflections) && model.reflections
|
208
|
+
refls && refls.each do |name, refl|
|
209
|
+
r[model.name][name] = case refl
|
210
|
+
when ActiveRecord::Reflection::ThroughReflection then {:macro => refl.macro, :model => refl.klass.name, :through => refl.through_reflection.name}
|
211
|
+
when ActiveRecord::Reflection::AssociationReflection then {:macro => refl.macro, :model => refl.klass.name}
|
212
|
+
else raise "Ups: #{refl.class}"
|
213
|
+
end
|
214
|
+
models.push refl.klass unless r.keys.include? refl.klass.name
|
215
|
+
end
|
216
|
+
end
|
217
|
+
r
|
218
|
+
end
|
219
|
+
|
182
220
|
def parse
|
183
221
|
benchmark 'SMQL parse' do
|
184
222
|
@conditions = ConditionTypes.try_parse @model, @query
|
@@ -187,11 +225,11 @@ class SmqlToAR
|
|
187
225
|
self
|
188
226
|
end
|
189
227
|
|
190
|
-
def build
|
228
|
+
def build prefix = nil, base_table = nil
|
191
229
|
benchmark 'SMQL build query' do
|
192
|
-
@builder = QueryBuilder.new @model
|
230
|
+
@builder = QueryBuilder.new @model, prefix, base_table
|
193
231
|
table = @builder.base_table
|
194
|
-
@conditions.each
|
232
|
+
@conditions.each &it.build( builder, table)
|
195
233
|
end
|
196
234
|
#p builder: @builder
|
197
235
|
self
|
metadata
CHANGED
@@ -5,8 +5,8 @@ version: !ruby/object:Gem::Version
|
|
5
5
|
segments:
|
6
6
|
- 0
|
7
7
|
- 0
|
8
|
-
-
|
9
|
-
version: 0.0.
|
8
|
+
- 3
|
9
|
+
version: 0.0.3
|
10
10
|
platform: ruby
|
11
11
|
authors:
|
12
12
|
- Denis Knauf
|
@@ -14,7 +14,7 @@ autorequire:
|
|
14
14
|
bindir: bin
|
15
15
|
cert_chain: []
|
16
16
|
|
17
|
-
date: 2011-09-
|
17
|
+
date: 2011-09-27 00:00:00 +02:00
|
18
18
|
default_executable:
|
19
19
|
dependencies:
|
20
20
|
- !ruby/object:Gem::Dependency
|
@@ -56,6 +56,19 @@ dependencies:
|
|
56
56
|
version: "0"
|
57
57
|
type: :runtime
|
58
58
|
version_requirements: *id003
|
59
|
+
- !ruby/object:Gem::Dependency
|
60
|
+
name: methodphitamine
|
61
|
+
prerelease: false
|
62
|
+
requirement: &id004 !ruby/object:Gem::Requirement
|
63
|
+
none: false
|
64
|
+
requirements:
|
65
|
+
- - ">="
|
66
|
+
- !ruby/object:Gem::Version
|
67
|
+
segments:
|
68
|
+
- 0
|
69
|
+
version: "0"
|
70
|
+
type: :runtime
|
71
|
+
version_requirements: *id004
|
59
72
|
description: SMQL is a JSON-based query langauage similar to MQL. This gem convertes these querys to ActiveRecord.
|
60
73
|
email:
|
61
74
|
- Denis.Knauf@gmail.com
|