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 CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA1:
3
- metadata.gz: 7aace3a8490ac510480e630683114815b46ce0a8
4
- data.tar.gz: 5e3c803aa0ce4e2df7b7cadc95e4bb9d07b57270
3
+ metadata.gz: 583d8e8209374ac577bd0837daba375f490e39de
4
+ data.tar.gz: 22f74200fc7f26da338a2c16f061dd83449aa406
5
5
  SHA512:
6
- metadata.gz: 5ed85bc2c763103d1f036910a1fe8c6a7effc47838b8666d0ce06c1d8b93b931cebd918b735627206714a0cdcf7294f54e24ab93953f615ab1cfa600b2399185
7
- data.tar.gz: e6b13eff65621c3ce3f995e80c94ca00ad0c5280667f82daaa0285821d06d564a7aa180b822fed07e2107bcf42fcaa321202442981c61edadf1f59c7a38c753c
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
@@ -0,0 +1,4 @@
1
+ id title
2
+ 1 Good book
3
+ 2 Greate book
4
+ 3 Perfect book
@@ -0,0 +1,5 @@
1
+ id user_id nickname
2
+ 1 1 yuki
3
+ 2 1 kuri
4
+ 3 1 k
5
+ 4 2 f
@@ -0,0 +1,4 @@
1
+ id user_id password
2
+ 1 1 abcdefg
3
+ 2 2 hijklmn
4
+ 3 3 opqrstu
@@ -0,0 +1,5 @@
1
+ id user_id book_id
2
+ 1 1 1
3
+ 2 1 2
4
+ 3 1 3
5
+ 4 2 2
@@ -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
- __send__ key
96
+ @attrs[key.to_sym]
70
97
  end
71
98
 
72
99
  def []=(key, value)
73
- __send__ "#{key}=", value
100
+ @attrs[key.to_sym] = value
74
101
  end
75
102
 
76
- def to_h
103
+ def attributes
77
104
  @attrs.dup
78
105
  end
79
106
 
80
107
  def ==(other)
81
- super || other.instance_of?(self.class) && to_h == other.to_h
108
+ super || other.instance_of?(self.class) && @attrs == other.attributes
82
109
  end
83
110
  alias eql? ==
84
111
  end
@@ -1,3 +1,9 @@
1
1
  module ActiveTsv
2
- Condition = Struct.new(:method_name, :values)
2
+ class Condition < Struct.new(:values)
3
+ class Equal < Condition
4
+ end
5
+
6
+ class NotEqual < Condition
7
+ end
8
+ end
3
9
  end
@@ -0,0 +1,5 @@
1
+ module ActiveTsv
2
+ ActiveTsvError = Class.new(StandardError)
3
+ RecordNotFound = Class.new(ActiveTsvError)
4
+ StatementInvalid = Class.new(ActiveTsvError)
5
+ end
@@ -1,15 +1,35 @@
1
+ # frozen_string_literal: true
2
+
1
3
  module ActiveTsv
2
- Ordering = Struct.new(:column)
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
- class Ascending < Ordering
5
- def to_i
6
- 1
16
+ def descending?
17
+ false
18
+ end
7
19
  end
8
- end
9
20
 
10
- class Descending < Ordering
11
- def to_i
12
- -1
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
@@ -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
@@ -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(:==, where_value)
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
- to_a.last
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
- to_a.take(n)
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 = to_a.take(11).map(&:inspect)
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
- 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
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? { |cond|
170
- cond.values.all? do |k, v|
171
- value[key_to_value_index[k]].__send__(cond.method_name, v.to_s)
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
@@ -1,5 +1,5 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module ActiveTsv
4
- VERSION = "0.2.0"
4
+ VERSION = "0.3.0"
5
5
  end
@@ -6,7 +6,7 @@ module ActiveTsv
6
6
 
7
7
  def not(condition)
8
8
  @relation.dup.tap do |r|
9
- r.where_values << Condition.new(:!=, condition)
9
+ r.where_values << Condition::NotEqual.new(condition)
10
10
  end
11
11
  end
12
12
  end
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.2.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-05-17 00:00:00.000000000 Z
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.4
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