active_tsv 0.2.0 → 0.3.0
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/README.md +29 -1
- data/active_tsv.gemspec +1 -0
- data/data/books.tsv +4 -0
- data/data/nicknames.tsv +5 -0
- data/data/passwords.tsv +4 -0
- data/data/user_books.tsv +5 -0
- data/lib/active_tsv/base.rb +32 -5
- data/lib/active_tsv/condition.rb +7 -1
- data/lib/active_tsv/errors.rb +5 -0
- data/lib/active_tsv/ordering.rb +28 -8
- data/lib/active_tsv/querying.rb +2 -2
- data/lib/active_tsv/reflection.rb +41 -0
- data/lib/active_tsv/relation.rb +78 -25
- data/lib/active_tsv/version.rb +1 -1
- data/lib/active_tsv/where_chain.rb +1 -1
- data/lib/active_tsv.rb +4 -0
- metadata +23 -3
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA1:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 583d8e8209374ac577bd0837daba375f490e39de
|
4
|
+
data.tar.gz: 22f74200fc7f26da338a2c16f061dd83449aa406
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: b6e48455014b19f9ce07a0aa54f1b11ba52329dda243ef9e8d9b0ccbc6f68d2ca642e731c4e5c40041373ae10e73282713d7a9e408b8ff105802652777d83e8c
|
7
|
+
data.tar.gz: 17eddd9dd791b10c7234350ae809bde5391ece5ee95e868ee1fb3b7f6788ac27ee2eff41b26f551e1b32e65974a4b8db7152c1751b2b1a4edcec93ccdeca461e
|
data/README.md
CHANGED
@@ -15,11 +15,30 @@ id name age
|
|
15
15
|
3 bar 30
|
16
16
|
```
|
17
17
|
|
18
|
+
data/nicknames.tsv
|
19
|
+
|
20
|
+
```tsv
|
21
|
+
id user_id nickname
|
22
|
+
1 1 yuki
|
23
|
+
2 1 kuri
|
24
|
+
3 1 k
|
25
|
+
4 2 f
|
26
|
+
```
|
27
|
+
|
18
28
|
```ruby
|
19
29
|
require 'active_tsv'
|
20
30
|
|
21
31
|
class User < ActiveTsv::Base
|
22
|
-
self.table_path = "data/users.tsv"
|
32
|
+
self.table_path = "data/users.tsv" # required
|
33
|
+
# self.encoding = Encoding::Shift_JIS # optional
|
34
|
+
# self.primary_key = "uid" # optional
|
35
|
+
# self.keys = %w(id name age) # optional
|
36
|
+
has_many :nicknames
|
37
|
+
end
|
38
|
+
|
39
|
+
class Nickname < ActiveTsv::Base
|
40
|
+
self.table_path = "data/nicknames.tsv"
|
41
|
+
belongs_to :user
|
23
42
|
end
|
24
43
|
|
25
44
|
User.all
|
@@ -45,6 +64,9 @@ User.where(age: 30).last
|
|
45
64
|
User.where(age: 30).where(name: "ksss").first
|
46
65
|
#=> #<User id: "1", name: "ksss", age: "30">
|
47
66
|
|
67
|
+
User.where(id: [1, 2]).to_a
|
68
|
+
#=> [#<User id: "1", name: "ksss", age: "30">, #<User id: "2", name: "foo", age: "29">]
|
69
|
+
|
48
70
|
User.where.not(name: "ksss").first
|
49
71
|
#=> #<User id: "2", name: "foo", age: "29">
|
50
72
|
|
@@ -56,6 +78,12 @@ User.order(:name).to_a
|
|
56
78
|
|
57
79
|
User.order(name: :desc).to_a
|
58
80
|
=> [#<User id: "1", name: "ksss", age: "30">, #<User id: "2", name: "foo", age: "29">, #<User id: "3", name: "bar", age: "30">]
|
81
|
+
|
82
|
+
User.first.nicknames
|
83
|
+
#=> #<ActiveTsv::Relation [#<Nickname id: "1", user_id: "1", nickname: "yuki">, #<Nickname id: "2", user_id: "1", nickname: "kuri">, #<Nickname id: "3", user_id: "1", nickname: "k">]>
|
84
|
+
|
85
|
+
Nickname.last.user
|
86
|
+
#=> #<User id: "2", name: "foo", age: "29">
|
59
87
|
```
|
60
88
|
|
61
89
|
Also Supported **CSV**.
|
data/active_tsv.gemspec
CHANGED
@@ -17,6 +17,7 @@ Gem::Specification.new do |spec|
|
|
17
17
|
spec.files = `git ls-files -z`.split("\x0").reject { |f| f.match(%r{_test.rb}) }
|
18
18
|
spec.require_paths = ["lib"]
|
19
19
|
|
20
|
+
spec.add_runtime_dependency "activesupport"
|
20
21
|
spec.add_development_dependency "bundler", "~> 1.11"
|
21
22
|
spec.add_development_dependency "rake", "~> 10.0"
|
22
23
|
spec.add_development_dependency "rgot"
|
data/data/books.tsv
ADDED
data/data/nicknames.tsv
ADDED
data/data/passwords.tsv
ADDED
data/data/user_books.tsv
ADDED
data/lib/active_tsv/base.rb
CHANGED
@@ -7,9 +7,11 @@ module ActiveTsv
|
|
7
7
|
# end
|
8
8
|
class Base
|
9
9
|
SEPARATER = "\t"
|
10
|
+
DEFAULT_PRIMARY_KEY = "id"
|
10
11
|
|
11
12
|
class << self
|
12
13
|
include Querying
|
14
|
+
include Reflection
|
13
15
|
|
14
16
|
attr_reader :table_path
|
15
17
|
|
@@ -42,12 +44,37 @@ module ActiveTsv
|
|
42
44
|
end
|
43
45
|
|
44
46
|
def open(&block)
|
45
|
-
CSV.open(table_path, col_sep: self::SEPARATER, &block)
|
47
|
+
CSV.open(table_path, "r:#{encoding}:UTF-8", col_sep: self::SEPARATER, &block)
|
46
48
|
end
|
47
49
|
|
48
50
|
def keys
|
49
51
|
@keys ||= open { |csv| csv.gets }.map(&:to_sym)
|
50
52
|
end
|
53
|
+
|
54
|
+
def keys=(headers)
|
55
|
+
@keys = headers.map(&:to_sym)
|
56
|
+
end
|
57
|
+
|
58
|
+
def primary_key
|
59
|
+
@primary_key ||= DEFAULT_PRIMARY_KEY
|
60
|
+
end
|
61
|
+
|
62
|
+
attr_writer :primary_key
|
63
|
+
|
64
|
+
def encoding
|
65
|
+
@encoding ||= Encoding::UTF_8
|
66
|
+
end
|
67
|
+
|
68
|
+
def encoding=(enc)
|
69
|
+
case enc
|
70
|
+
when String
|
71
|
+
@encoding = Encoding.find(enc)
|
72
|
+
when Encoding
|
73
|
+
@encoding = enc
|
74
|
+
else
|
75
|
+
raise ArgumentError, "#{enc.class} dose not support"
|
76
|
+
end
|
77
|
+
end
|
51
78
|
end
|
52
79
|
|
53
80
|
def initialize(attrs = {})
|
@@ -66,19 +93,19 @@ module ActiveTsv
|
|
66
93
|
end
|
67
94
|
|
68
95
|
def [](key)
|
69
|
-
|
96
|
+
@attrs[key.to_sym]
|
70
97
|
end
|
71
98
|
|
72
99
|
def []=(key, value)
|
73
|
-
|
100
|
+
@attrs[key.to_sym] = value
|
74
101
|
end
|
75
102
|
|
76
|
-
def
|
103
|
+
def attributes
|
77
104
|
@attrs.dup
|
78
105
|
end
|
79
106
|
|
80
107
|
def ==(other)
|
81
|
-
super || other.instance_of?(self.class) &&
|
108
|
+
super || other.instance_of?(self.class) && @attrs == other.attributes
|
82
109
|
end
|
83
110
|
alias eql? ==
|
84
111
|
end
|
data/lib/active_tsv/condition.rb
CHANGED
data/lib/active_tsv/ordering.rb
CHANGED
@@ -1,15 +1,35 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
1
3
|
module ActiveTsv
|
2
|
-
Ordering
|
4
|
+
class Ordering < Struct.new(:column)
|
5
|
+
VALID_DIRECTIONS = [:asc, :desc, :ASC, :DESC, "asc", "desc", "ASC", "DESC"]
|
6
|
+
|
7
|
+
class Ascending < Ordering
|
8
|
+
def to_i
|
9
|
+
1
|
10
|
+
end
|
11
|
+
|
12
|
+
def ascending?
|
13
|
+
true
|
14
|
+
end
|
3
15
|
|
4
|
-
|
5
|
-
|
6
|
-
|
16
|
+
def descending?
|
17
|
+
false
|
18
|
+
end
|
7
19
|
end
|
8
|
-
end
|
9
20
|
|
10
|
-
|
11
|
-
|
12
|
-
|
21
|
+
class Descending < Ordering
|
22
|
+
def to_i
|
23
|
+
-1
|
24
|
+
end
|
25
|
+
|
26
|
+
def ascending?
|
27
|
+
false
|
28
|
+
end
|
29
|
+
|
30
|
+
def descending?
|
31
|
+
true
|
32
|
+
end
|
13
33
|
end
|
14
34
|
end
|
15
35
|
end
|
data/lib/active_tsv/querying.rb
CHANGED
@@ -1,8 +1,8 @@
|
|
1
1
|
module ActiveTsv
|
2
2
|
module Querying
|
3
|
-
METHODS = %i(first last take where count order group pluck)
|
3
|
+
METHODS = %i(find first last take where count order group pluck minimum maximum)
|
4
4
|
METHODS.each do |m|
|
5
|
-
module_eval <<-DEFINE_METHOD, __FILE__, __LINE__
|
5
|
+
module_eval <<-DEFINE_METHOD, __FILE__, __LINE__ + 1
|
6
6
|
def #{m}(*args, &block)
|
7
7
|
all.#{m}(*args, &block)
|
8
8
|
end
|
@@ -0,0 +1,41 @@
|
|
1
|
+
module ActiveTsv
|
2
|
+
module Reflection
|
3
|
+
def has_many(name, through: nil)
|
4
|
+
if through
|
5
|
+
class_eval <<-CODE, __FILE__, __LINE__ + 1
|
6
|
+
def #{name}
|
7
|
+
#{name.to_s.classify}.where(
|
8
|
+
#{name.to_s.classify}.primary_key => #{through}.pluck(:#{name.to_s.singularize.underscore}_id)
|
9
|
+
)
|
10
|
+
end
|
11
|
+
CODE
|
12
|
+
else
|
13
|
+
class_eval <<-CODE, __FILE__, __LINE__ + 1
|
14
|
+
def #{name}
|
15
|
+
#{name.to_s.singularize.classify}.where(
|
16
|
+
#{self.name.underscore}_id: self[self.class.primary_key]
|
17
|
+
)
|
18
|
+
end
|
19
|
+
CODE
|
20
|
+
end
|
21
|
+
end
|
22
|
+
|
23
|
+
def has_one(name)
|
24
|
+
class_eval <<-CODE, __FILE__, __LINE__ + 1
|
25
|
+
def #{name}
|
26
|
+
#{name.to_s.singularize.classify}.where(
|
27
|
+
#{self.name.underscore}_id: self[self.class.primary_key]
|
28
|
+
).first
|
29
|
+
end
|
30
|
+
CODE
|
31
|
+
end
|
32
|
+
|
33
|
+
def belongs_to(name)
|
34
|
+
class_eval <<-CODE, __FILE__, __LINE__ + 1
|
35
|
+
def #{name}
|
36
|
+
#{name.to_s.classify}.where(self.class.primary_key => self["#{name}_id"]).first
|
37
|
+
end
|
38
|
+
CODE
|
39
|
+
end
|
40
|
+
end
|
41
|
+
end
|
data/lib/active_tsv/relation.rb
CHANGED
@@ -5,7 +5,6 @@ module ActiveTsv
|
|
5
5
|
include Enumerable
|
6
6
|
|
7
7
|
BUF_SIZE = 1024
|
8
|
-
VALID_DIRECTIONS = [:asc, :desc, :ASC, :DESC, "asc", "desc", "ASC", "DESC"]
|
9
8
|
|
10
9
|
attr_reader :model
|
11
10
|
attr_accessor :where_values
|
@@ -31,10 +30,30 @@ module ActiveTsv
|
|
31
30
|
group_values == other.group_values
|
32
31
|
end
|
33
32
|
|
33
|
+
def find(*ids)
|
34
|
+
case ids.length
|
35
|
+
when 0
|
36
|
+
raise ActiveTsv::RecordNotFound, "Couldn't find #{@model} without an ID"
|
37
|
+
when 1
|
38
|
+
id = ids.first
|
39
|
+
record = where(@model.primary_key => id).first
|
40
|
+
unless record
|
41
|
+
raise ActiveTsv::RecordNotFound, "Couldn't find #{@model} with '#{@model.primary_key}'=#{id}"
|
42
|
+
end
|
43
|
+
record
|
44
|
+
else
|
45
|
+
records = where(@model.primary_key => ids).to_a
|
46
|
+
unless ids.length == records.length
|
47
|
+
raise ActiveTsv::RecordNotFound, "Couldn't find all #{@model} with '#{@model.primary_key}': (#{ids.join(', ')}) (found #{records.length} results, but was looking for #{ids.length})"
|
48
|
+
end
|
49
|
+
records
|
50
|
+
end
|
51
|
+
end
|
52
|
+
|
34
53
|
def where(where_value = nil)
|
35
54
|
if where_value
|
36
55
|
dup.tap do |r|
|
37
|
-
r.where_values << Condition.new(
|
56
|
+
r.where_values << Condition::Equal.new(where_value)
|
38
57
|
end
|
39
58
|
else
|
40
59
|
WhereChain.new(dup)
|
@@ -46,10 +65,10 @@ module ActiveTsv
|
|
46
65
|
if fields.empty?
|
47
66
|
to_value_a
|
48
67
|
elsif fields.one?
|
49
|
-
field = fields.first
|
50
|
-
to_value_a.map { |v| v[key_to_value_index[field]] }
|
68
|
+
field = fields.first.to_sym
|
69
|
+
to_value_a.map! { |v| v[key_to_value_index[field]] }
|
51
70
|
else
|
52
|
-
to_value_a.map { |v| fields.map { |field| v[key_to_value_index[field]] } }
|
71
|
+
to_value_a.map! { |v| fields.map { |field| v[key_to_value_index[field.to_sym]] } }
|
53
72
|
end
|
54
73
|
end
|
55
74
|
|
@@ -67,7 +86,7 @@ module ActiveTsv
|
|
67
86
|
|
68
87
|
def last
|
69
88
|
if @where_values.empty? && @order_values.empty?
|
70
|
-
last_value = File.open(@model.table_path) do |f|
|
89
|
+
last_value = File.open(@model.table_path, "r:#{@model.encoding}:UTF-8") do |f|
|
71
90
|
f.seek(0, IO::SEEK_END)
|
72
91
|
buf_size = [f.size, self.class::BUF_SIZE].min
|
73
92
|
while true
|
@@ -83,7 +102,7 @@ module ActiveTsv
|
|
83
102
|
end
|
84
103
|
@model.new(CSV.new(last_value, col_sep: @model::SEPARATER).shift)
|
85
104
|
else
|
86
|
-
|
105
|
+
@model.new(to_value_a.last)
|
87
106
|
end
|
88
107
|
end
|
89
108
|
|
@@ -92,7 +111,7 @@ module ActiveTsv
|
|
92
111
|
if @order_values.empty?
|
93
112
|
each_model.take(n)
|
94
113
|
else
|
95
|
-
|
114
|
+
to_value_a.take(n).map! { |i| @model.new(i) }
|
96
115
|
end
|
97
116
|
else
|
98
117
|
first
|
@@ -132,27 +151,42 @@ module ActiveTsv
|
|
132
151
|
end
|
133
152
|
|
134
153
|
def to_a
|
135
|
-
to_value_a.map { |v| @model.new(v) }
|
154
|
+
to_value_a.map! { |v| @model.new(v) }
|
136
155
|
end
|
137
156
|
|
138
157
|
def inspect
|
139
|
-
a =
|
158
|
+
a = to_value_a.take(11).map! { |i| @model.new(i) }.map!(&:inspect)
|
140
159
|
a[10] = '...' if a.length == 11
|
141
160
|
|
142
161
|
"#<#{self.class.name} [#{a.join(', ')}]>"
|
143
162
|
end
|
144
163
|
|
164
|
+
def maximum(column)
|
165
|
+
pluck(column).max
|
166
|
+
end
|
167
|
+
|
168
|
+
def minimum(column)
|
169
|
+
pluck(column).min
|
170
|
+
end
|
171
|
+
|
145
172
|
private
|
146
173
|
|
147
174
|
def to_value_a
|
148
175
|
ret = each_value.to_a
|
149
|
-
key_to_value_index = @model.keys.each_with_index.to_h
|
150
176
|
if @order_values.empty?.!
|
151
|
-
|
152
|
-
|
153
|
-
|
154
|
-
|
155
|
-
|
177
|
+
key_to_value_index = @model.keys.each_with_index.to_h
|
178
|
+
if @order_values.one?
|
179
|
+
order_condition = @order_values.first
|
180
|
+
index = key_to_value_index[order_condition.column]
|
181
|
+
ret.sort_by! { |i| i[index] }
|
182
|
+
ret.reverse! if order_condition.descending?
|
183
|
+
else
|
184
|
+
ret.sort! do |a, b|
|
185
|
+
@order_values.each.with_index(1) do |order_condition, index|
|
186
|
+
comp = a[key_to_value_index[order_condition.column]] <=> b[key_to_value_index[order_condition.column]]
|
187
|
+
break 0 if comp == 0 && index == @order_values.length
|
188
|
+
break comp * order_condition.to_i if comp != 0
|
189
|
+
end
|
156
190
|
end
|
157
191
|
end
|
158
192
|
end
|
@@ -166,11 +200,30 @@ module ActiveTsv
|
|
166
200
|
@model.open do |csv|
|
167
201
|
csv.gets
|
168
202
|
csv.each do |value|
|
169
|
-
yield value if @where_values.all?
|
170
|
-
cond
|
171
|
-
|
203
|
+
yield value if @where_values.all? do |cond|
|
204
|
+
case cond
|
205
|
+
when Condition::Equal
|
206
|
+
cond.values.all? do |k, v|
|
207
|
+
index = key_to_value_index[k.to_sym]
|
208
|
+
raise StatementInvalid, "no such column: #{k}" unless index
|
209
|
+
if v.respond_to?(:to_a)
|
210
|
+
v.to_a.any? { |vv| value[index] == vv.to_s }
|
211
|
+
else
|
212
|
+
value[index] == v.to_s
|
213
|
+
end
|
214
|
+
end
|
215
|
+
when Condition::NotEqual
|
216
|
+
cond.values.all? do |k, v|
|
217
|
+
index = key_to_value_index[k.to_sym]
|
218
|
+
raise StatementInvalid, "no such column: #{k}" unless index
|
219
|
+
if v.respond_to?(:to_a)
|
220
|
+
!v.to_a.any? { |vv| value[index] == vv.to_s }
|
221
|
+
else
|
222
|
+
!(value[index] == v.to_s)
|
223
|
+
end
|
224
|
+
end
|
172
225
|
end
|
173
|
-
|
226
|
+
end
|
174
227
|
end
|
175
228
|
end
|
176
229
|
end
|
@@ -185,17 +238,17 @@ module ActiveTsv
|
|
185
238
|
columns.map { |column|
|
186
239
|
case column
|
187
240
|
when Symbol
|
188
|
-
Ascending.new(column)
|
241
|
+
Ordering::Ascending.new(column)
|
189
242
|
when Hash
|
190
243
|
column.map do |col, direction|
|
191
|
-
unless VALID_DIRECTIONS.include?(direction)
|
192
|
-
raise ArgumentError, %(Direction "#{direction}" is invalid. Valid directions are: #{VALID_DIRECTIONS})
|
244
|
+
unless Ordering::VALID_DIRECTIONS.include?(direction)
|
245
|
+
raise ArgumentError, %(Direction "#{direction}" is invalid. Valid directions are: #{Ordering::VALID_DIRECTIONS})
|
193
246
|
end
|
194
247
|
case direction.downcase.to_sym
|
195
248
|
when :asc
|
196
|
-
Ascending.new(col)
|
249
|
+
Ordering::Ascending.new(col)
|
197
250
|
when :desc
|
198
|
-
Descending.new(col)
|
251
|
+
Ordering::Descending.new(col)
|
199
252
|
end
|
200
253
|
end
|
201
254
|
end
|
data/lib/active_tsv/version.rb
CHANGED
data/lib/active_tsv.rb
CHANGED
@@ -2,10 +2,14 @@
|
|
2
2
|
|
3
3
|
require 'csv'
|
4
4
|
|
5
|
+
require "active_support/inflector"
|
6
|
+
|
5
7
|
require "active_tsv/querying"
|
8
|
+
require "active_tsv/reflection"
|
6
9
|
require "active_tsv/relation"
|
7
10
|
require "active_tsv/where_chain"
|
8
11
|
require "active_tsv/condition"
|
9
12
|
require "active_tsv/ordering"
|
13
|
+
require "active_tsv/errors"
|
10
14
|
require "active_tsv/base"
|
11
15
|
require "active_tsv/version"
|
metadata
CHANGED
@@ -1,15 +1,29 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: active_tsv
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 0.
|
4
|
+
version: 0.3.0
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- ksss
|
8
8
|
autorequire:
|
9
9
|
bindir: bin
|
10
10
|
cert_chain: []
|
11
|
-
date: 2016-
|
11
|
+
date: 2016-11-24 00:00:00.000000000 Z
|
12
12
|
dependencies:
|
13
|
+
- !ruby/object:Gem::Dependency
|
14
|
+
name: activesupport
|
15
|
+
requirement: !ruby/object:Gem::Requirement
|
16
|
+
requirements:
|
17
|
+
- - ">="
|
18
|
+
- !ruby/object:Gem::Version
|
19
|
+
version: '0'
|
20
|
+
type: :runtime
|
21
|
+
prerelease: false
|
22
|
+
version_requirements: !ruby/object:Gem::Requirement
|
23
|
+
requirements:
|
24
|
+
- - ">="
|
25
|
+
- !ruby/object:Gem::Version
|
26
|
+
version: '0'
|
13
27
|
- !ruby/object:Gem::Dependency
|
14
28
|
name: bundler
|
15
29
|
requirement: !ruby/object:Gem::Requirement
|
@@ -70,15 +84,21 @@ files:
|
|
70
84
|
- bin/setup
|
71
85
|
- data/benchmark.png
|
72
86
|
- data/benchmark.rb
|
87
|
+
- data/books.tsv
|
73
88
|
- data/names.tsv
|
89
|
+
- data/nicknames.tsv
|
90
|
+
- data/passwords.tsv
|
91
|
+
- data/user_books.tsv
|
74
92
|
- data/users.csv
|
75
93
|
- data/users.tsv
|
76
94
|
- lib/active_csv.rb
|
77
95
|
- lib/active_tsv.rb
|
78
96
|
- lib/active_tsv/base.rb
|
79
97
|
- lib/active_tsv/condition.rb
|
98
|
+
- lib/active_tsv/errors.rb
|
80
99
|
- lib/active_tsv/ordering.rb
|
81
100
|
- lib/active_tsv/querying.rb
|
101
|
+
- lib/active_tsv/reflection.rb
|
82
102
|
- lib/active_tsv/relation.rb
|
83
103
|
- lib/active_tsv/version.rb
|
84
104
|
- lib/active_tsv/where_chain.rb
|
@@ -102,7 +122,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
|
|
102
122
|
version: '0'
|
103
123
|
requirements: []
|
104
124
|
rubyforge_project:
|
105
|
-
rubygems_version: 2.6.
|
125
|
+
rubygems_version: 2.6.8
|
106
126
|
signing_key:
|
107
127
|
specification_version: 4
|
108
128
|
summary: A Class of Active record pattern for TSV/CSV
|