active_tsv 0.2.0 → 0.3.0
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/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
|