smql 0.0.2 → 0.0.3
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/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
|