active_tsv 0.1.0 → 0.2.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 CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA1:
3
- metadata.gz: fa1189fb2be4c0a87c3e95522ea91a23cf35f956
4
- data.tar.gz: 18aa344f90494fe0a3d42ae74688a2771598eed9
3
+ metadata.gz: 7aace3a8490ac510480e630683114815b46ce0a8
4
+ data.tar.gz: 5e3c803aa0ce4e2df7b7cadc95e4bb9d07b57270
5
5
  SHA512:
6
- metadata.gz: c905b4740b81faf9f7068c5da07f4b1f5805cee13fa1667ce2f05d6bbf190481e71bdfd0819af2402202d79b77da6f37b371c5b4d2541be1d4c6b994b6bd00f4
7
- data.tar.gz: 14d82e1931e8aaee4643d5a2caa6ec443d269d7bee6c5b130a0b34f68ef254ec131fa6482c3db7d2ed1a5d521ad5cfea5792a4f6fe8967a4f17d8b3f2ac96419
6
+ metadata.gz: 5ed85bc2c763103d1f036910a1fe8c6a7effc47838b8666d0ce06c1d8b93b931cebd918b735627206714a0cdcf7294f54e24ab93953f615ab1cfa600b2399185
7
+ data.tar.gz: e6b13eff65621c3ce3f995e80c94ca00ad0c5280667f82daaa0285821d06d564a7aa180b822fed07e2107bcf42fcaa321202442981c61edadf1f59c7a38c753c
data/README.md CHANGED
@@ -17,25 +17,45 @@ id name age
17
17
 
18
18
  ```ruby
19
19
  require 'active_tsv'
20
+
20
21
  class User < ActiveTsv::Base
21
22
  self.table_path = "data/users.tsv"
22
23
  end
23
24
 
25
+ User.all
26
+ => #<ActiveTsv::Relation [#<User id: "1", name: "ksss", age: "30">, #<User id: "2", name: "foo", age: "29">, #<User id: "3", name: "bar", age: "30">]>
27
+ User.all.to_a
28
+ => [#<User id: "1", name: "ksss", age: "30">, #<User id: "2", name: "foo", age: "29">, #<User id: "3", name: "bar", age: "30">]
29
+
24
30
  User.first
25
- #=> #<User {:id=>"1", :name=>"ksss", :age=>"30"}>
31
+ #=> #<User id: "1", name: "ksss", age: "30">
26
32
  User.last
27
- #=> #<User {:id=>"3", :name=>"bar", :age=>"30"}>
33
+ #=> #<User id: "3", name: "bar", age: "30">
34
+
28
35
  User.where(age: 30).each do |user|
29
36
  user.name #=> "ksss", "bar"
30
37
  end
38
+
31
39
  User.where(age: 30).to_a
32
- #=> [#<User {:id=>"1", :name=>"ksss", :age=>"30"}>, #<User {:id=>"3", :name=>"bar", :age=>"30"}>]
40
+ #=> [#<User id: "1", name: "ksss", age: "30">, #<User id: "3", name: "bar", age: "30">]
41
+
33
42
  User.where(age: 30).last
34
- #=> #<User {:id=>"3", :name=>"bar", :age=>"30"}>
43
+ #=> #<User id: "3", name: "bar", age: "30">
44
+
35
45
  User.where(age: 30).where(name: "ksss").first
36
- #=> #<User {:id=>"1", :name=>"ksss", :age=>"30"}>
46
+ #=> #<User id: "1", name: "ksss", age: "30">
47
+
37
48
  User.where.not(name: "ksss").first
38
- #=> #<User {:id=>"2", :name=>"foo", :age=>"29"}>
49
+ #=> #<User id: "2", name: "foo", age: "29">
50
+
51
+ User.group(:age).count
52
+ #=> {"30"=>2, "29"=>1}
53
+
54
+ User.order(:name).to_a
55
+ #=> [#<User id: "3", name: "bar", age: "30">, #<User id: "2", name: "foo", age: "29">, #<User id: "1", name: "ksss", age: "30">]
56
+
57
+ User.order(name: :desc).to_a
58
+ => [#<User id: "1", name: "ksss", age: "30">, #<User id: "2", name: "foo", age: "29">, #<User id: "3", name: "bar", age: "30">]
39
59
  ```
40
60
 
41
61
  Also Supported **CSV**.
@@ -49,7 +69,7 @@ end
49
69
 
50
70
  ## Goal
51
71
 
52
- Support all methods that like ActiveRecord
72
+ Support all methods of ActiveRecord
53
73
 
54
74
  ## Installation
55
75
 
Binary file
data/data/benchmark.rb ADDED
@@ -0,0 +1,104 @@
1
+ #! /usr/bin/env ruby
2
+
3
+ require 'active_tsv'
4
+ require 'tempfile'
5
+ require 'active_hash'
6
+ require 'csv'
7
+ require 'objspace'
8
+ require 'stringio'
9
+
10
+ module ActiveHashTsv
11
+ class Base < ActiveFile::Base
12
+ SEPARATER = "\t"
13
+ extend ActiveFile::HashAndArrayFiles
14
+ class << self
15
+ def load_file
16
+ raw_data
17
+ end
18
+
19
+ def extension
20
+ "tsv"
21
+ end
22
+
23
+ private
24
+
25
+ def load_path(path)
26
+ data = []
27
+ CSV.open(path, col_sep: self::SEPARATER) do |csv|
28
+ keys = csv.gets.map(&:to_sym)
29
+ while line = csv.gets
30
+ data << keys.zip(line).to_h
31
+ end
32
+ end
33
+ data
34
+ end
35
+ end
36
+ end
37
+ end
38
+
39
+ def open_csv_with_temp_table(n)
40
+ headers = [*'a'..'j']
41
+ Tempfile.create(["", ".tsv"]) do |f|
42
+ f.puts headers.join("\t")
43
+ n.times do |i|
44
+ f.puts [*1..(headers.length)].map{ |j| i * j }.join("\t")
45
+ end
46
+ f.close
47
+ yield f.path
48
+ end
49
+ end
50
+ io = StringIO.new
51
+ $stdout = io
52
+ def b
53
+ GC.start
54
+ before = ObjectSpace.memsize_of_all
55
+ realtime = Benchmark.realtime {
56
+ yield
57
+ }
58
+ GC.start
59
+ mem = (ObjectSpace.memsize_of_all - before).to_f
60
+ [realtime, mem]
61
+ end
62
+ puts "title\tActiveHash\tActiveTsv\ttActiveHash\ttActiveTsv"
63
+ ns = [100, 200, 300, 400, 500]
64
+ ns.each do |n|
65
+ open_csv_with_temp_table(n) do |path|
66
+ hr, hm = b {
67
+ h = Class.new(ActiveHashTsv::Base) do
68
+ set_root_path File.dirname(path)
69
+ set_filename File.basename(path).sub(/\..*/, '')
70
+ end
71
+ h.all.each {}
72
+ }
73
+ tr, tm = b {
74
+ t = Class.new(ActiveTsv::Base) do
75
+ self.table_path = path
76
+ end
77
+ t.all.each {}
78
+ }
79
+ puts sprintf("%d\t%0.5f\t%0.5f\t%d\t%d", n, hr, tr, hm, tm)
80
+
81
+ hr, hm = b {
82
+ h = Class.new(ActiveHashTsv::Base) do
83
+ set_root_path File.dirname(path)
84
+ set_filename File.basename(path).sub(/\..*/, '')
85
+ end
86
+ h.where(a: '10').first
87
+ }
88
+
89
+ tr, tm = b {
90
+ t = Class.new(ActiveTsv::Base) do
91
+ self.table_path = path
92
+ end
93
+ t.where(a: '10').first
94
+ }
95
+ puts sprintf("%d\t%0.5f\t%0.5f\t%d\t%d", n, hr, tr, hm, tm)
96
+ end
97
+ end
98
+ $stdout = STDOUT
99
+ io.rewind
100
+ puts io.gets
101
+ lines = io.each_line.to_a
102
+ 2.times do |i|
103
+ puts lines.values_at(i, i+2, i+4, i+6, i+8)
104
+ end
data/data/users.tsv CHANGED
@@ -1,4 +1,4 @@
1
1
  id name age
2
- 1 ksss 30
2
+ 1 "ksss" 30
3
3
  2 foo 29
4
4
  3 bar 30
@@ -7,9 +7,10 @@ module ActiveTsv
7
7
  # end
8
8
  class Base
9
9
  SEPARATER = "\t"
10
- BUF_SIZE = 1024
11
10
 
12
11
  class << self
12
+ include Querying
13
+
13
14
  attr_reader :table_path
14
15
 
15
16
  def table_path=(path)
@@ -33,31 +34,11 @@ module ActiveTsv
33
34
  end
34
35
 
35
36
  def all
36
- Relation.new(self, [])
37
+ Relation.new(self)
37
38
  end
38
39
 
39
- def first
40
- first_value = open { |csv| csv.gets; csv.gets }
41
- new(keys.zip(first_value).to_h)
42
- end
43
-
44
- def last
45
- last_value = File.open(table_path) do |f|
46
- f.seek(0, IO::SEEK_END)
47
- size = f.size
48
- buf_size = [size, self::BUF_SIZE].min
49
- while true
50
- f.seek(-buf_size, IO::SEEK_CUR)
51
- buf = f.read(buf_size)
52
- if index = buf.rindex($INPUT_RECORD_SEPARATOR, -2)
53
- f.seek(-buf_size + index + 1, IO::SEEK_CUR)
54
- break f.read.chomp
55
- else
56
- f.seek(-buf_size, IO::SEEK_CUR)
57
- end
58
- end
59
- end
60
- new(keys.zip(CSV.new(last_value, col_sep: self::SEPARATER).shift).to_h)
40
+ def scope(name, proc)
41
+ define_singleton_method(name, &proc)
61
42
  end
62
43
 
63
44
  def open(&block)
@@ -67,30 +48,21 @@ module ActiveTsv
67
48
  def keys
68
49
  @keys ||= open { |csv| csv.gets }.map(&:to_sym)
69
50
  end
70
-
71
- def where(condition = nil)
72
- all.where(condition)
73
- end
74
-
75
- def count
76
- all.count
77
- end
78
-
79
- def order(*columns)
80
- all.order(*columns)
81
- end
82
51
  end
83
52
 
84
53
  def initialize(attrs = {})
85
- unless attrs.kind_of?(Hash)
54
+ case attrs
55
+ when Hash
56
+ @attrs = attrs
57
+ when Array
58
+ @attrs = self.class.keys.zip(attrs).to_h
59
+ else
86
60
  raise ArgumentError, "#{attrs.class} is not supported value"
87
61
  end
88
-
89
- @attrs = attrs
90
62
  end
91
63
 
92
64
  def inspect
93
- "#<#{self.class} #{to_h}>"
65
+ "#<#{self.class} #{@attrs.map { |k, v| "#{k}: #{v.inspect}" }.join(', ')}>"
94
66
  end
95
67
 
96
68
  def [](key)
@@ -104,5 +76,10 @@ module ActiveTsv
104
76
  def to_h
105
77
  @attrs.dup
106
78
  end
79
+
80
+ def ==(other)
81
+ super || other.instance_of?(self.class) && to_h == other.to_h
82
+ end
83
+ alias eql? ==
107
84
  end
108
85
  end
@@ -0,0 +1,15 @@
1
+ module ActiveTsv
2
+ Ordering = Struct.new(:column)
3
+
4
+ class Ascending < Ordering
5
+ def to_i
6
+ 1
7
+ end
8
+ end
9
+
10
+ class Descending < Ordering
11
+ def to_i
12
+ -1
13
+ end
14
+ end
15
+ end
@@ -0,0 +1,12 @@
1
+ module ActiveTsv
2
+ module Querying
3
+ METHODS = %i(first last take where count order group pluck)
4
+ METHODS.each do |m|
5
+ module_eval <<-DEFINE_METHOD, __FILE__, __LINE__
6
+ def #{m}(*args, &block)
7
+ all.#{m}(*args, &block)
8
+ end
9
+ DEFINE_METHOD
10
+ end
11
+ end
12
+ end
@@ -4,12 +4,31 @@ module ActiveTsv
4
4
  class Relation
5
5
  include Enumerable
6
6
 
7
+ BUF_SIZE = 1024
8
+ VALID_DIRECTIONS = [:asc, :desc, :ASC, :DESC, "asc", "desc", "ASC", "DESC"]
9
+
7
10
  attr_reader :model
8
- attr_reader :where_values
9
- def initialize(model, where_values, order_values = [])
11
+ attr_accessor :where_values
12
+ attr_accessor :order_values
13
+ attr_accessor :group_values
14
+
15
+ def initialize(model)
10
16
  @model = model
11
- @where_values = where_values
12
- @order_values = order_values
17
+ @where_values = []
18
+ @order_values = []
19
+ @group_values = []
20
+ end
21
+
22
+ def initialize_copy(copy)
23
+ copy.where_values = where_values.dup
24
+ copy.order_values = order_values.dup
25
+ copy.group_values = group_values.dup
26
+ end
27
+
28
+ def ==(other)
29
+ where_values == other.where_values &&
30
+ order_values == other.order_values &&
31
+ group_values == other.group_values
13
32
  end
14
33
 
15
34
  def where(where_value = nil)
@@ -22,16 +41,89 @@ module ActiveTsv
22
41
  end
23
42
  end
24
43
 
44
+ def pluck(*fields)
45
+ key_to_value_index = @model.keys.each_with_index.to_h
46
+ if fields.empty?
47
+ to_value_a
48
+ elsif fields.one?
49
+ field = fields.first
50
+ to_value_a.map { |v| v[key_to_value_index[field]] }
51
+ else
52
+ to_value_a.map { |v| fields.map { |field| v[key_to_value_index[field]] } }
53
+ end
54
+ end
55
+
25
56
  def exists?
26
57
  !first.nil?
27
58
  end
28
59
 
60
+ def first
61
+ if @order_values.empty?
62
+ each_model.first
63
+ else
64
+ to_a.first
65
+ end
66
+ end
67
+
29
68
  def last
30
- to_a.last
69
+ if @where_values.empty? && @order_values.empty?
70
+ last_value = File.open(@model.table_path) do |f|
71
+ f.seek(0, IO::SEEK_END)
72
+ buf_size = [f.size, self.class::BUF_SIZE].min
73
+ while true
74
+ f.seek(-buf_size, IO::SEEK_CUR)
75
+ buf = f.read(buf_size)
76
+ if index = buf.rindex($INPUT_RECORD_SEPARATOR, -2)
77
+ f.seek(-buf_size + index + 1, IO::SEEK_CUR)
78
+ break f.read.chomp
79
+ else
80
+ f.seek(-buf_size, IO::SEEK_CUR)
81
+ end
82
+ end
83
+ end
84
+ @model.new(CSV.new(last_value, col_sep: @model::SEPARATER).shift)
85
+ else
86
+ to_a.last
87
+ end
88
+ end
89
+
90
+ def take(n = nil)
91
+ if n
92
+ if @order_values.empty?
93
+ each_model.take(n)
94
+ else
95
+ to_a.take(n)
96
+ end
97
+ else
98
+ first
99
+ end
100
+ end
101
+
102
+ def count
103
+ if @group_values.empty?
104
+ super
105
+ else
106
+ h = if @group_values.one?
107
+ group_by { |i| i[@group_values.first] }
108
+ else
109
+ group_by { |i| @group_values.map { |c| i[c] } }
110
+ end
111
+ h.each do |k, v|
112
+ h[k] = v.count
113
+ end
114
+ h
115
+ end
31
116
  end
32
117
 
33
118
  def order(*columns)
34
- @order_values += columns
119
+ @order_values += order_conditions(columns)
120
+ @order_values.uniq!
121
+ self
122
+ end
123
+
124
+ def group(*columns)
125
+ @group_values += columns
126
+ @group_values.uniq!
35
127
  self
36
128
  end
37
129
 
@@ -40,26 +132,74 @@ module ActiveTsv
40
132
  end
41
133
 
42
134
  def to_a
43
- ret = []
44
- keys = @model.keys
45
- key_to_value_index = keys.each_with_index.map { |k, index| [k, index] }.to_h
135
+ to_value_a.map { |v| @model.new(v) }
136
+ end
137
+
138
+ def inspect
139
+ a = to_a.take(11).map(&:inspect)
140
+ a[10] = '...' if a.length == 11
141
+
142
+ "#<#{self.class.name} [#{a.join(', ')}]>"
143
+ end
144
+
145
+ private
146
+
147
+ def to_value_a
148
+ ret = each_value.to_a
149
+ key_to_value_index = @model.keys.each_with_index.to_h
150
+ if @order_values.empty?.!
151
+ ret.sort! do |a, b|
152
+ @order_values.each.with_index(1) do |order_condition, index|
153
+ comp = a[key_to_value_index[order_condition.column]] <=> b[key_to_value_index[order_condition.column]]
154
+ break 0 if comp == 0 && index == @order_values.length
155
+ break comp * order_condition.to_i if comp != 0
156
+ end
157
+ end
158
+ end
159
+ ret
160
+ end
161
+
162
+ def each_value
163
+ return to_enum(__method__) unless block_given?
164
+
165
+ key_to_value_index = @model.keys.each_with_index.to_h
46
166
  @model.open do |csv|
47
167
  csv.gets
48
168
  csv.each do |value|
49
- ret << @model.new(keys.zip(value).to_h) if @where_values.all? { |cond|
169
+ yield value if @where_values.all? { |cond|
50
170
  cond.values.all? do |k, v|
51
171
  value[key_to_value_index[k]].__send__(cond.method_name, v.to_s)
52
172
  end
53
173
  }
54
174
  end
55
175
  end
56
- if @order_values.empty?
57
- ret
58
- else
59
- ret.sort_by do |i|
60
- @order_values.map { |m| i[m] }.join('-')
176
+ end
177
+
178
+ def each_model
179
+ return to_enum(__method__) unless block_given?
180
+
181
+ each_value { |v| yield @model.new(v) }
182
+ end
183
+
184
+ def order_conditions(columns)
185
+ columns.map { |column|
186
+ case column
187
+ when Symbol
188
+ Ascending.new(column)
189
+ when Hash
190
+ column.map do |col, direction|
191
+ unless VALID_DIRECTIONS.include?(direction)
192
+ raise ArgumentError, %(Direction "#{direction}" is invalid. Valid directions are: #{VALID_DIRECTIONS})
193
+ end
194
+ case direction.downcase.to_sym
195
+ when :asc
196
+ Ascending.new(col)
197
+ when :desc
198
+ Descending.new(col)
199
+ end
200
+ end
61
201
  end
62
- end
202
+ }.flatten
63
203
  end
64
204
  end
65
205
  end
@@ -1,5 +1,5 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module ActiveTsv
4
- VERSION = "0.1.0"
4
+ VERSION = "0.2.0"
5
5
  end
data/lib/active_tsv.rb CHANGED
@@ -2,8 +2,10 @@
2
2
 
3
3
  require 'csv'
4
4
 
5
- require "active_tsv/base"
5
+ require "active_tsv/querying"
6
6
  require "active_tsv/relation"
7
7
  require "active_tsv/where_chain"
8
8
  require "active_tsv/condition"
9
+ require "active_tsv/ordering"
10
+ require "active_tsv/base"
9
11
  require "active_tsv/version"
metadata CHANGED
@@ -1,14 +1,14 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: active_tsv
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.1.0
4
+ version: 0.2.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-05-06 00:00:00.000000000 Z
11
+ date: 2016-05-17 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: bundler
@@ -68,6 +68,8 @@ files:
68
68
  - active_tsv.gemspec
69
69
  - bin/console
70
70
  - bin/setup
71
+ - data/benchmark.png
72
+ - data/benchmark.rb
71
73
  - data/names.tsv
72
74
  - data/users.csv
73
75
  - data/users.tsv
@@ -75,6 +77,8 @@ files:
75
77
  - lib/active_tsv.rb
76
78
  - lib/active_tsv/base.rb
77
79
  - lib/active_tsv/condition.rb
80
+ - lib/active_tsv/ordering.rb
81
+ - lib/active_tsv/querying.rb
78
82
  - lib/active_tsv/relation.rb
79
83
  - lib/active_tsv/version.rb
80
84
  - lib/active_tsv/where_chain.rb
@@ -98,7 +102,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
98
102
  version: '0'
99
103
  requirements: []
100
104
  rubyforge_project:
101
- rubygems_version: 2.6.3
105
+ rubygems_version: 2.6.4
102
106
  signing_key:
103
107
  specification_version: 4
104
108
  summary: A Class of Active record pattern for TSV/CSV