rom-sql 0.9.1 → 1.0.0.beta1
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.
- checksums.yaml +4 -4
- data/.travis.yml +1 -1
- data/CHANGELOG.md +32 -0
- data/Gemfile +4 -1
- data/lib/rom/plugins/relation/sql/auto_wrap.rb +1 -3
- data/lib/rom/sql/association.rb +33 -14
- data/lib/rom/sql/association/many_to_many.rb +17 -10
- data/lib/rom/sql/association/many_to_one.rb +29 -13
- data/lib/rom/sql/association/name.rb +12 -4
- data/lib/rom/sql/association/one_to_many.rb +21 -10
- data/lib/rom/sql/commands/create.rb +0 -1
- data/lib/rom/sql/commands/update.rb +1 -49
- data/lib/rom/sql/dsl.rb +29 -0
- data/lib/rom/sql/expression.rb +26 -0
- data/lib/rom/sql/function.rb +23 -0
- data/lib/rom/sql/gateway.rb +24 -9
- data/lib/rom/sql/migration.rb +6 -7
- data/lib/rom/sql/migration/migrator.rb +7 -8
- data/lib/rom/sql/order_dsl.rb +20 -0
- data/lib/rom/sql/plugin/associates.rb +58 -45
- data/lib/rom/sql/plugin/pagination.rb +8 -11
- data/lib/rom/sql/plugins.rb +0 -2
- data/lib/rom/sql/projection_dsl.rb +41 -0
- data/lib/rom/sql/qualified_attribute.rb +2 -2
- data/lib/rom/sql/relation.rb +35 -67
- data/lib/rom/sql/relation/reading.rb +77 -25
- data/lib/rom/sql/restriction_dsl.rb +24 -0
- data/lib/rom/sql/schema.rb +73 -7
- data/lib/rom/sql/schema/associations_dsl.rb +4 -3
- data/lib/rom/sql/schema/dsl.rb +5 -2
- data/lib/rom/sql/schema/inferrer.rb +21 -11
- data/lib/rom/sql/transaction.rb +19 -0
- data/lib/rom/sql/type.rb +76 -0
- data/lib/rom/sql/version.rb +1 -1
- data/rom-sql.gemspec +3 -4
- data/spec/extensions/postgres/inferrer_spec.rb +19 -9
- data/spec/integration/association/many_to_many/custom_fks_spec.rb +73 -0
- data/spec/integration/association/many_to_many/from_view_spec.rb +81 -0
- data/spec/integration/association/many_to_many_spec.rb +2 -2
- data/spec/integration/association/many_to_one/custom_fks_spec.rb +59 -0
- data/spec/integration/association/many_to_one/from_view_spec.rb +74 -0
- data/spec/integration/association/many_to_one/self_ref_spec.rb +51 -0
- data/spec/integration/association/many_to_one_spec.rb +4 -2
- data/spec/integration/association/one_to_many/custom_fks_spec.rb +48 -0
- data/spec/integration/association/one_to_many/from_view_spec.rb +57 -0
- data/spec/integration/association/one_to_many/self_ref_spec.rb +52 -0
- data/spec/integration/association/one_to_many_spec.rb +1 -1
- data/spec/integration/association/one_to_one_spec.rb +1 -1
- data/spec/integration/association/one_to_one_through_spec.rb +2 -2
- data/spec/integration/commands/create_spec.rb +11 -27
- data/spec/integration/commands/update_spec.rb +54 -109
- data/spec/integration/gateway_spec.rb +31 -17
- data/spec/integration/plugins/associates_spec.rb +27 -0
- data/spec/integration/plugins/auto_wrap_spec.rb +8 -8
- data/spec/integration/schema/call_spec.rb +24 -0
- data/spec/integration/schema/prefix_spec.rb +18 -0
- data/spec/integration/schema/qualified_spec.rb +18 -0
- data/spec/integration/schema/rename_spec.rb +23 -0
- data/spec/integration/schema/view_spec.rb +29 -0
- data/spec/integration/schema_inference_spec.rb +31 -14
- data/spec/spec_helper.rb +2 -2
- data/spec/support/helpers.rb +7 -0
- data/spec/unit/gateway_spec.rb +5 -4
- data/spec/unit/projection_dsl_spec.rb +54 -0
- data/spec/unit/relation/dataset_spec.rb +3 -3
- data/spec/unit/relation/distinct_spec.rb +8 -7
- data/spec/unit/relation/exclude_spec.rb +2 -4
- data/spec/unit/relation/having_spec.rb +6 -4
- data/spec/unit/relation/inner_join_spec.rb +47 -2
- data/spec/unit/relation/invert_spec.rb +2 -3
- data/spec/unit/relation/left_join_spec.rb +44 -3
- data/spec/unit/relation/order_spec.rb +40 -0
- data/spec/unit/relation/prefix_spec.rb +2 -0
- data/spec/unit/relation/project_spec.rb +3 -1
- data/spec/unit/relation/qualified_columns_spec.rb +2 -0
- data/spec/unit/relation/rename_spec.rb +2 -0
- data/spec/unit/relation/right_join_spec.rb +59 -0
- data/spec/unit/relation/select_append_spec.rb +21 -0
- data/spec/unit/relation/select_spec.rb +41 -0
- data/spec/unit/relation/where_spec.rb +28 -0
- data/spec/unit/restriction_dsl_spec.rb +34 -0
- metadata +62 -40
- data/lib/rom/plugins/relation/sql/base_view.rb +0 -31
- data/lib/rom/sql/header.rb +0 -61
- data/lib/rom/sql/plugin/assoc_macros.rb +0 -133
- data/lib/rom/sql/plugin/assoc_macros/class_interface.rb +0 -128
- data/spec/integration/read_spec.rb +0 -111
- data/spec/unit/association_errors_spec.rb +0 -19
- data/spec/unit/plugin/assoc_macros/combined_associations_spec.rb +0 -73
- data/spec/unit/plugin/assoc_macros/many_to_many_spec.rb +0 -53
- data/spec/unit/plugin/assoc_macros/many_to_one_spec.rb +0 -61
- data/spec/unit/plugin/assoc_macros/one_to_many_spec.rb +0 -78
- data/spec/unit/plugin/base_view_spec.rb +0 -18
@@ -1,4 +1,4 @@
|
|
1
|
-
require '
|
1
|
+
require 'dry/core/cache'
|
2
2
|
|
3
3
|
module ROM
|
4
4
|
module SQL
|
@@ -10,7 +10,7 @@ module ROM
|
|
10
10
|
class QualifiedAttribute
|
11
11
|
include Dry::Equalizer(:dataset, :attribute)
|
12
12
|
|
13
|
-
extend Cache
|
13
|
+
extend Dry::Core::Cache
|
14
14
|
|
15
15
|
# Dataset (table) name
|
16
16
|
#
|
data/lib/rom/sql/relation.rb
CHANGED
@@ -1,19 +1,14 @@
|
|
1
|
-
require 'rom/sql/header'
|
2
1
|
require 'rom/sql/types'
|
3
|
-
|
4
2
|
require 'rom/sql/schema'
|
5
3
|
|
6
4
|
require 'rom/sql/relation/reading'
|
7
5
|
require 'rom/sql/relation/writing'
|
8
6
|
|
9
|
-
require 'rom/plugins/relation/view'
|
10
7
|
require 'rom/plugins/relation/key_inference'
|
11
|
-
require 'rom/plugins/relation/sql/base_view'
|
12
8
|
require 'rom/plugins/relation/sql/auto_combine'
|
13
9
|
require 'rom/plugins/relation/sql/auto_wrap'
|
14
10
|
|
15
|
-
require '
|
16
|
-
require 'rom/support/constants'
|
11
|
+
require 'dry/core/deprecations'
|
17
12
|
|
18
13
|
module ROM
|
19
14
|
module SQL
|
@@ -26,8 +21,6 @@ module ROM
|
|
26
21
|
adapter :sql
|
27
22
|
|
28
23
|
use :key_inference
|
29
|
-
use :view
|
30
|
-
use :base_view
|
31
24
|
use :auto_combine
|
32
25
|
use :auto_wrap
|
33
26
|
|
@@ -42,31 +35,45 @@ module ROM
|
|
42
35
|
|
43
36
|
klass.class_eval do
|
44
37
|
schema_dsl SQL::Schema::DSL
|
45
|
-
|
38
|
+
|
39
|
+
schema_inferrer -> (name, gateway) do
|
46
40
|
inferrer_for_db = ROM::SQL::Schema::Inferrer.get(gateway.connection.database_type.to_sym)
|
47
|
-
|
41
|
+
begin
|
42
|
+
inferrer_for_db.new.call(name, gateway)
|
43
|
+
rescue Sequel::Error => e
|
44
|
+
ROM::Schema::DEFAULT_INFERRER.()
|
45
|
+
end
|
48
46
|
end
|
49
47
|
|
50
48
|
dataset do
|
49
|
+
# TODO: feels strange to do it here - we need a new hook for this during finalization
|
50
|
+
klass.define_default_views!
|
51
|
+
schema = klass.schema
|
52
|
+
|
51
53
|
table = opts[:from].first
|
52
54
|
|
53
55
|
if db.table_exists?(table)
|
54
|
-
|
55
|
-
|
56
|
-
|
56
|
+
if schema
|
57
|
+
select(*schema.map(&:to_sym)).order(*schema.project(*schema.primary_key_names).qualified.map(&:to_sym))
|
58
|
+
else
|
59
|
+
select(*columns).order(*klass.primary_key_columns(db, table))
|
60
|
+
end
|
57
61
|
else
|
58
62
|
self
|
59
63
|
end
|
60
64
|
end
|
65
|
+
end
|
66
|
+
end
|
61
67
|
|
62
|
-
|
63
|
-
|
64
|
-
|
65
|
-
|
66
|
-
|
67
|
-
|
68
|
-
|
69
|
-
|
68
|
+
# @api private
|
69
|
+
def self.define_default_views!
|
70
|
+
# @!method by_pk(pk)
|
71
|
+
# Return a relation restricted by its primary key
|
72
|
+
# @param [Object] pk The primary key value
|
73
|
+
# @return [SQL::Relation]
|
74
|
+
# @api public
|
75
|
+
view(:by_pk, schema.map(&:name)) do |pk|
|
76
|
+
where(primary_key => pk)
|
70
77
|
end
|
71
78
|
end
|
72
79
|
|
@@ -76,16 +83,9 @@ module ROM
|
|
76
83
|
end
|
77
84
|
|
78
85
|
# @api private
|
79
|
-
def self.
|
80
|
-
names =
|
81
|
-
|
82
|
-
schema.primary_key_names
|
83
|
-
elsif db.respond_to?(:primary_key)
|
84
|
-
Array(db.primary_key(table))
|
85
|
-
else
|
86
|
-
[:id]
|
87
|
-
end
|
88
|
-
Header.new(names, table)
|
86
|
+
def self.primary_key_columns(db, table)
|
87
|
+
names = db.respond_to?(:primary_key) ? Array(db.primary_key(table)) : [:id]
|
88
|
+
names.map { |col| :"#{table}__#{col}" }
|
89
89
|
end
|
90
90
|
|
91
91
|
# Set primary key
|
@@ -94,8 +94,10 @@ module ROM
|
|
94
94
|
#
|
95
95
|
# @api public
|
96
96
|
def self.primary_key(value)
|
97
|
-
Deprecations.announce(
|
98
|
-
:primary_key,
|
97
|
+
Dry::Core::Deprecations.announce(
|
98
|
+
:primary_key,
|
99
|
+
"use schema definition to configure primary key",
|
100
|
+
tag: :rom
|
99
101
|
)
|
100
102
|
option :primary_key, reader: true, default: value
|
101
103
|
end
|
@@ -104,26 +106,6 @@ module ROM
|
|
104
106
|
rel.schema? ? rel.schema.primary_key_name : :id
|
105
107
|
}
|
106
108
|
|
107
|
-
# Return table name from relation's sql statement
|
108
|
-
#
|
109
|
-
# This value is used by `header` for prefixing column names
|
110
|
-
#
|
111
|
-
# @return [Symbol]
|
112
|
-
#
|
113
|
-
# @api private
|
114
|
-
def table
|
115
|
-
@table ||= dataset.opts[:from].first
|
116
|
-
end
|
117
|
-
|
118
|
-
# Return a header for this relation
|
119
|
-
#
|
120
|
-
# @return [Header]
|
121
|
-
#
|
122
|
-
# @api private
|
123
|
-
def header
|
124
|
-
@header ||= Header.new(selected_columns, table)
|
125
|
-
end
|
126
|
-
|
127
109
|
# Return raw column names
|
128
110
|
#
|
129
111
|
# @return [Array<Symbol>]
|
@@ -132,20 +114,6 @@ module ROM
|
|
132
114
|
def columns
|
133
115
|
@columns ||= dataset.columns
|
134
116
|
end
|
135
|
-
|
136
|
-
protected
|
137
|
-
|
138
|
-
# Return a list of columns from *the sql select* statement or default to
|
139
|
-
# dataset columns
|
140
|
-
#
|
141
|
-
# This is used to construct relation's header
|
142
|
-
#
|
143
|
-
# @return [Array<Symbol>]
|
144
|
-
#
|
145
|
-
# @api private
|
146
|
-
def selected_columns
|
147
|
-
@selected_columns ||= dataset.opts.fetch(:select, columns)
|
148
|
-
end
|
149
117
|
end
|
150
118
|
end
|
151
119
|
end
|
@@ -1,3 +1,5 @@
|
|
1
|
+
require 'dry/core/inflector'
|
2
|
+
|
1
3
|
module ROM
|
2
4
|
module SQL
|
3
5
|
class Relation < ROM::Relation
|
@@ -72,8 +74,8 @@ module ROM
|
|
72
74
|
# @return [Relation]
|
73
75
|
#
|
74
76
|
# @api public
|
75
|
-
def prefix(name = Inflector.singularize(
|
76
|
-
|
77
|
+
def prefix(name = Dry::Core::Inflector.singularize(schema.name.dataset))
|
78
|
+
schema.prefix(name).(self)
|
77
79
|
end
|
78
80
|
|
79
81
|
# Qualifies all columns in a relation
|
@@ -87,7 +89,7 @@ module ROM
|
|
87
89
|
#
|
88
90
|
# @api public
|
89
91
|
def qualified
|
90
|
-
|
92
|
+
schema.qualified.(self)
|
91
93
|
end
|
92
94
|
|
93
95
|
# Return a list of qualified column names
|
@@ -102,7 +104,7 @@ module ROM
|
|
102
104
|
#
|
103
105
|
# @api public
|
104
106
|
def qualified_columns
|
105
|
-
|
107
|
+
schema.qualified.map(&:to_sym)
|
106
108
|
end
|
107
109
|
|
108
110
|
# Map tuples from the relation
|
@@ -152,7 +154,7 @@ module ROM
|
|
152
154
|
#
|
153
155
|
# @api public
|
154
156
|
def project(*names)
|
155
|
-
|
157
|
+
schema.project(*names).(self)
|
156
158
|
end
|
157
159
|
|
158
160
|
# Rename columns in a relation
|
@@ -169,7 +171,7 @@ module ROM
|
|
169
171
|
#
|
170
172
|
# @api public
|
171
173
|
def rename(options)
|
172
|
-
|
174
|
+
schema.rename(options).(self)
|
173
175
|
end
|
174
176
|
|
175
177
|
# Select specific columns for select clause
|
@@ -182,7 +184,7 @@ module ROM
|
|
182
184
|
#
|
183
185
|
# @api public
|
184
186
|
def select(*args, &block)
|
185
|
-
|
187
|
+
schema.project(*args, &block).(self)
|
186
188
|
end
|
187
189
|
|
188
190
|
# Append specific columns to select clause
|
@@ -197,7 +199,7 @@ module ROM
|
|
197
199
|
#
|
198
200
|
# @api public
|
199
201
|
def select_append(*args, &block)
|
200
|
-
|
202
|
+
schema.merge(self.class.schema.project(*args, &block)).(self)
|
201
203
|
end
|
202
204
|
|
203
205
|
# Returns a copy of the relation with a SQL DISTINCT clause.
|
@@ -211,7 +213,7 @@ module ROM
|
|
211
213
|
#
|
212
214
|
# @api public
|
213
215
|
def distinct(*args, &block)
|
214
|
-
|
216
|
+
new(dataset.__send__(__method__, *args, &block))
|
215
217
|
end
|
216
218
|
|
217
219
|
# Returns a result of SQL SUM clause.
|
@@ -288,7 +290,11 @@ module ROM
|
|
288
290
|
#
|
289
291
|
# @api public
|
290
292
|
def where(*args, &block)
|
291
|
-
|
293
|
+
if block
|
294
|
+
new(dataset.where(*args).where(self.class.schema.restriction(&block)))
|
295
|
+
else
|
296
|
+
new(dataset.__send__(__method__, *args))
|
297
|
+
end
|
292
298
|
end
|
293
299
|
|
294
300
|
# Restrict a relation to not match criteria
|
@@ -302,7 +308,7 @@ module ROM
|
|
302
308
|
#
|
303
309
|
# @api public
|
304
310
|
def exclude(*args, &block)
|
305
|
-
|
311
|
+
new(dataset.__send__(__method__, *args, &block))
|
306
312
|
end
|
307
313
|
|
308
314
|
# Restrict a relation to match grouping criteria
|
@@ -320,7 +326,11 @@ module ROM
|
|
320
326
|
#
|
321
327
|
# @api public
|
322
328
|
def having(*args, &block)
|
323
|
-
|
329
|
+
if block
|
330
|
+
new(dataset.having(*args).having(self.class.schema.restriction(&block)))
|
331
|
+
else
|
332
|
+
new(dataset.__send__(__method__, *args, &block))
|
333
|
+
end
|
324
334
|
end
|
325
335
|
|
326
336
|
# Inverts the current WHERE and HAVING clauses. If there is neither a
|
@@ -336,13 +346,14 @@ module ROM
|
|
336
346
|
#
|
337
347
|
# @api public
|
338
348
|
def invert
|
339
|
-
|
349
|
+
new(dataset.invert)
|
340
350
|
end
|
341
351
|
|
342
352
|
# Set order for the relation
|
343
353
|
#
|
344
354
|
# @example
|
345
355
|
# users.order(:name)
|
356
|
+
# users.order { [name.desc, id.qualified.desc]}
|
346
357
|
#
|
347
358
|
# @param [Array<Symbol>] *args A list with column names
|
348
359
|
#
|
@@ -350,7 +361,11 @@ module ROM
|
|
350
361
|
#
|
351
362
|
# @api public
|
352
363
|
def order(*args, &block)
|
353
|
-
|
364
|
+
if block
|
365
|
+
new(dataset.order(*args, *self.class.schema.order(&block)))
|
366
|
+
else
|
367
|
+
new(dataset.__send__(__method__, *args, &block))
|
368
|
+
end
|
354
369
|
end
|
355
370
|
|
356
371
|
# Reverse the order of the relation
|
@@ -362,7 +377,7 @@ module ROM
|
|
362
377
|
#
|
363
378
|
# @api public
|
364
379
|
def reverse(*args, &block)
|
365
|
-
|
380
|
+
new(dataset.__send__(__method__, *args, &block))
|
366
381
|
end
|
367
382
|
|
368
383
|
# Limit a relation to a specific number of tuples
|
@@ -379,7 +394,7 @@ module ROM
|
|
379
394
|
#
|
380
395
|
# @api public
|
381
396
|
def limit(*args, &block)
|
382
|
-
|
397
|
+
new(dataset.__send__(__method__, *args, &block))
|
383
398
|
end
|
384
399
|
|
385
400
|
# Set offset for the relation
|
@@ -393,7 +408,7 @@ module ROM
|
|
393
408
|
#
|
394
409
|
# @api public
|
395
410
|
def offset(*args, &block)
|
396
|
-
|
411
|
+
new(dataset.__send__(__method__, *args, &block))
|
397
412
|
end
|
398
413
|
|
399
414
|
# Join with another relation using INNER JOIN
|
@@ -407,9 +422,10 @@ module ROM
|
|
407
422
|
# @return [Relation]
|
408
423
|
#
|
409
424
|
# @api public
|
410
|
-
def
|
411
|
-
|
425
|
+
def join(*args, &block)
|
426
|
+
__join__(__method__, *args, &block)
|
412
427
|
end
|
428
|
+
alias_method :inner_join, :join
|
413
429
|
|
414
430
|
# Join other relation using LEFT OUTER JOIN
|
415
431
|
#
|
@@ -423,7 +439,23 @@ module ROM
|
|
423
439
|
#
|
424
440
|
# @api public
|
425
441
|
def left_join(*args, &block)
|
426
|
-
|
442
|
+
__join__(__method__, *args, &block)
|
443
|
+
end
|
444
|
+
|
445
|
+
# Join other relation using RIGHT JOIN
|
446
|
+
#
|
447
|
+
# @example
|
448
|
+
# users.right_join(:tasks, id: :user_id)
|
449
|
+
# users.right_join(tasks)
|
450
|
+
#
|
451
|
+
# @param [Symbol] relation name
|
452
|
+
# @param [Hash] join keys
|
453
|
+
#
|
454
|
+
# @return [Relation]
|
455
|
+
#
|
456
|
+
# @api public
|
457
|
+
def right_join(*args, &block)
|
458
|
+
__join__(__method__, *args, &block)
|
427
459
|
end
|
428
460
|
|
429
461
|
# Group by specific columns
|
@@ -437,7 +469,7 @@ module ROM
|
|
437
469
|
#
|
438
470
|
# @api public
|
439
471
|
def group(*args, &block)
|
440
|
-
|
472
|
+
new(dataset.__send__(__method__, *args, &block))
|
441
473
|
end
|
442
474
|
|
443
475
|
# Group by specific columns and count by group
|
@@ -452,7 +484,7 @@ module ROM
|
|
452
484
|
#
|
453
485
|
# @api public
|
454
486
|
def group_and_count(*args, &block)
|
455
|
-
|
487
|
+
new(dataset.__send__(__method__, *args, &block))
|
456
488
|
end
|
457
489
|
|
458
490
|
# Select and group by specific columns
|
@@ -467,7 +499,8 @@ module ROM
|
|
467
499
|
#
|
468
500
|
# @api public
|
469
501
|
def select_group(*args, &block)
|
470
|
-
|
502
|
+
new_schema = schema.project(*args, &block)
|
503
|
+
new_schema.(self).group(*new_schema)
|
471
504
|
end
|
472
505
|
|
473
506
|
# Adds a UNION clause for relation dataset using second relation dataset
|
@@ -487,7 +520,7 @@ module ROM
|
|
487
520
|
#
|
488
521
|
# @api public
|
489
522
|
def union(relation, options = EMPTY_HASH, &block)
|
490
|
-
|
523
|
+
new(dataset.__send__(__method__, relation.dataset, options, &block))
|
491
524
|
end
|
492
525
|
|
493
526
|
# Return if a restricted relation has 0 tuples
|
@@ -519,7 +552,26 @@ module ROM
|
|
519
552
|
#
|
520
553
|
# @api public
|
521
554
|
def read(sql)
|
522
|
-
|
555
|
+
new(dataset.db[sql])
|
556
|
+
end
|
557
|
+
|
558
|
+
private
|
559
|
+
|
560
|
+
# @api private
|
561
|
+
def __join__(type, other, opts = EMPTY_HASH, &block)
|
562
|
+
case other
|
563
|
+
when Symbol, Association::Name
|
564
|
+
new(dataset.__send__(type, other.to_sym, opts, &block))
|
565
|
+
when Relation
|
566
|
+
__send__(type, other.name.dataset, join_keys(other))
|
567
|
+
else
|
568
|
+
raise ArgumentError, "+other+ must be either a symbol or a relation, #{other.class} given"
|
569
|
+
end
|
570
|
+
end
|
571
|
+
|
572
|
+
# @api private
|
573
|
+
def join_keys(other)
|
574
|
+
other.associations[name].join_keys(__registry__)
|
523
575
|
end
|
524
576
|
end
|
525
577
|
end
|
@@ -0,0 +1,24 @@
|
|
1
|
+
require 'rom/sql/dsl'
|
2
|
+
require 'rom/sql/expression'
|
3
|
+
|
4
|
+
module ROM
|
5
|
+
module SQL
|
6
|
+
class RestrictionDSL < DSL
|
7
|
+
# @api private
|
8
|
+
def call(&block)
|
9
|
+
instance_exec(&block)
|
10
|
+
end
|
11
|
+
|
12
|
+
private
|
13
|
+
|
14
|
+
# @api private
|
15
|
+
def method_missing(meth, *args, &block)
|
16
|
+
if schema.key?(meth)
|
17
|
+
::ROM::SQL::Expression.new(schema[meth])
|
18
|
+
else
|
19
|
+
::Sequel::VIRTUAL_ROW.__send__(meth, *args, &block)
|
20
|
+
end
|
21
|
+
end
|
22
|
+
end
|
23
|
+
end
|
24
|
+
end
|