active_tsv 0.1.0 → 0.2.0

Sign up to get free protection for your applications and to get access to all the features.
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