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 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