record 1.1.0 → 1.1.1

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: bde8b7ec3eaa3f8a683b147cab7b9d25c581ae93
4
- data.tar.gz: 5d8a5490efb3b2cce94b1d444736b0bb059aee6d
3
+ metadata.gz: 905b06c2d1e681e1ae4a2f99b31e1293ab8d455d
4
+ data.tar.gz: 9a2876f8c7ca014181d75a1e0e02052b17c1546f
5
5
  SHA512:
6
- metadata.gz: ea21ef1ff6823d00f54f114fefecc8dfce49e541707b5e80af719ba517075d927bfdb69b3ea200c79d660e9cc0e0ff25871bf0dd64d5b635605dbd887b6dd620
7
- data.tar.gz: 88bd0b81f611bb908ad749f1bd8873c50c0df5745f96319e3e97cba01d808b23f37324bd067f038a897ff21593c4656c335989cb5bab1cd8ac945d2b5161e479
6
+ metadata.gz: a6a98f18f4b684db22b4099ccd0bf2a962d7cde44216facd03de97733000ddff62120709ab3766b85fadef58d5e60b88871a278282f9d7a3451cf6432a4edf67
7
+ data.tar.gz: c48238cbdedd1f50f36aad3ecbc5e9b437af5fd8f356bda68e1d16f5182c5d5af2d1d7866eb0d9a6d04832aff459fec1a133cf2af1a6af746a093aaab30ec485
data/Manifest.txt CHANGED
@@ -4,4 +4,10 @@ Manifest.txt
4
4
  README.md
5
5
  Rakefile
6
6
  lib/record.rb
7
+ lib/record/base.rb
8
+ lib/record/builder.rb
9
+ lib/record/field.rb
7
10
  lib/record/version.rb
11
+ test/helper.rb
12
+ test/test_record.rb
13
+ test/test_version.rb
@@ -0,0 +1,172 @@
1
+ # encoding: utf-8
2
+
3
+ module Record
4
+
5
+ class Base
6
+
7
+ def self.fields ## note: use class instance variable (@fields and NOT @@fields)!!!! (derived classes get its own copy!!!)
8
+ @fields ||= []
9
+ end
10
+
11
+ def self.field_names ## rename to header - why? why not?
12
+ ## return header row, that is, all field names in an array
13
+ ## todo: rename to field_names or just names - why? why not?
14
+ ## note: names are (always) symbols!!!
15
+ fields.map {|field| field.name }
16
+ end
17
+
18
+ def self.field_types
19
+ ## note: types are (always) classes!!!
20
+ fields.map {|field| field.type }
21
+ end
22
+
23
+
24
+
25
+ def self.field( name, type=:string )
26
+ num = fields.size ## auto-calc num(ber) / position index - always gets added at the end
27
+ field = Field.new( name, num, type )
28
+ fields << field
29
+
30
+ define_field( field ) ## auto-add getter,setter,parse/typecast
31
+ end
32
+
33
+ def self.define_field( field )
34
+ name = field.name ## note: always assumes a "cleaned-up" (symbol) name
35
+ num = field.num
36
+
37
+ define_method( name ) do
38
+ instance_variable_get( "@values" )[num]
39
+ end
40
+
41
+ define_method( "#{name}=" ) do |value|
42
+ instance_variable_get( "@values" )[num] = value
43
+ end
44
+
45
+ define_method( "parse_#{name}") do |value|
46
+ instance_variable_get( "@values" )[num] = field.typecast( value )
47
+ end
48
+ end
49
+
50
+ ## column/columns aliases for field/fields
51
+ ## use self << with alias_method - possible? works? why? why not?
52
+ def self.column( name, type=:string ) field( name, type ); end
53
+ def self.columns() fields; end
54
+ def self.column_names() field_names; end
55
+ def self.column_types() field_types; end
56
+
57
+
58
+
59
+
60
+ def self.build_hash( values ) ## find a better name - build_attrib? or something?
61
+ ## convert to key-value (attribute) pairs
62
+ ## puts "== build_hash:"
63
+ ## pp values
64
+
65
+ ## e.g. [[],[]] return zipped pairs in array as (attribute - name/value pair) hash
66
+ Hash[ field_names.zip(values) ]
67
+ end
68
+
69
+
70
+
71
+ def self.typecast( new_values )
72
+ values = []
73
+
74
+ ##
75
+ ## todo: check that new_values.size <= fields.size
76
+ ##
77
+ ## fields without values will get auto-filled with nils (or default field values?)
78
+
79
+ ##
80
+ ## use fields.zip( new_values ).map |field,value| ... instead - why? why not?
81
+ fields.each_with_index do |field,i|
82
+ value = new_values[i] ## note: returns nil if new_values.size < fields.size
83
+ values << field.typecast( value )
84
+ end
85
+ values
86
+ end
87
+
88
+
89
+ def parse( new_values ) ## use read (from array) or read_values or read_row - why? why not?
90
+
91
+ ## todo: check if values overshadowing values attrib is ok (without warning?) - use just new_values (not values)
92
+ @values = self.class.typecast( new_values )
93
+ self ## return self for chaining
94
+ end
95
+
96
+
97
+ def values
98
+ ## return array of all record values (typed e.g. float, integer, date, ..., that is,
99
+ ## as-is and NOT auto-converted to string
100
+ @values
101
+ end
102
+
103
+
104
+
105
+ def [](key)
106
+ if key.is_a? Integer
107
+ @values[ key ]
108
+ elsif key.is_a? Symbol
109
+ ## try attribute access
110
+ send( key )
111
+ else ## assume string
112
+ ## downcase and symbol-ize
113
+ ## remove spaces too -why? why not?
114
+ ## todo/fix: add a lookup mapping for (string) titles (Team 1, etc.)
115
+ send( key.downcase.to_sym )
116
+ end
117
+ end
118
+
119
+
120
+
121
+ def to_h ## use to_hash - why? why not? - add attributes alias - why? why not?
122
+ self.class.build_hash( @values )
123
+ end
124
+
125
+ def initialize( **kwargs )
126
+ @values = []
127
+ update( kwargs )
128
+ end
129
+
130
+ def update( **kwargs )
131
+ pp kwargs
132
+ kwargs.each do |name,value|
133
+ ## note: only convert/typecast string values
134
+ if value.is_a?( String )
135
+ send( "parse_#{name}", value ) ## note: use parse_<name> setter (for typecasting)
136
+ else ## use "regular" plain/classic attribute setter
137
+ send( "#{name}=", value )
138
+ end
139
+ end
140
+
141
+ ## todo: check if args.first is an array (init/update from array)
142
+ self ## return self for chaining
143
+ end
144
+ end ## class Base
145
+
146
+
147
+
148
+ #########
149
+ # alternative class (record) builder
150
+
151
+ def self.define( super_class=Base, &block ) ## check: rename super_class to base - why? why not?
152
+ builder = Builder.new( super_class )
153
+ if block.arity == 1
154
+ block.call( builder )
155
+ ## e.g. allows "yield" dsl style e.g.
156
+ ## Record.define do |rec|
157
+ ## rec.string :team1
158
+ ## rec.string :team2
159
+ ## end
160
+ ##
161
+ else
162
+ builder.instance_eval( &block )
163
+ ## e.g. shorter "instance eval" dsl style e.g.
164
+ ## Record.define do
165
+ ## string :team1
166
+ ## string :team2
167
+ ## end
168
+ end
169
+ builder.to_record
170
+ end
171
+
172
+ end # module Record
@@ -0,0 +1,37 @@
1
+ # encoding: utf-8
2
+
3
+ ## (record) builder mini language / domain-specific language (dsl)
4
+
5
+ module Record
6
+
7
+ class Builder # check: rename to RecordDefinition or RecordDsl or similar - why? why not?
8
+ def initialize( super_class=Base )
9
+ @clazz = Class.new( super_class )
10
+ end
11
+
12
+ def field( name, type=:string ) ## note: type defaults to string
13
+ puts " adding field >#{name}< with type >#{type}<"
14
+ @clazz.field( name, type ) ## auto-add getter and setter
15
+ end
16
+
17
+ def string( name )
18
+ puts " adding string field >#{name}<"
19
+ field( name, :string )
20
+ end
21
+
22
+ def integer( name ) ## use number for alias for integer - why? why not???
23
+ puts " adding integer number field >#{name}<"
24
+ field( name, :integer )
25
+ end
26
+
27
+ def float( name )
28
+ puts " adding float number field >#{name}<"
29
+ field( name, :float )
30
+ end
31
+
32
+
33
+ def to_record ## check: rename to just record or obj or finish or end something?
34
+ @clazz
35
+ end
36
+ end # class Builder
37
+ end # module Record
@@ -0,0 +1,80 @@
1
+ # encoding: utf-8
2
+
3
+
4
+ module Record
5
+
6
+ ## note on naming:
7
+ ## use naming convention from awk and tabular data package/json schema for now
8
+ ## use - records (use rows for "raw" untyped (string) data rows )
9
+ ## - fields (NOT columns or attributes) -- might add an alias later - why? why not?
10
+
11
+ class Field ## ruby record class field
12
+ attr_reader :name, :type
13
+
14
+ ## zero-based position index (0,1,2,3,...)
15
+ ## todo: use/rename to index/idx/pos - add an alias name - why?
16
+ attr_reader :num
17
+
18
+ def initialize( name, num, type )
19
+ ## note: always symbol-ify (to_sym) name and type
20
+
21
+ ## todo: add title or titles for header field/column title as is e.g. 'Team 1' etc.
22
+ ## incl. numbers only or even an empty header title
23
+ @name = name.to_sym
24
+ @num = num
25
+
26
+ if type.is_a?( Class )
27
+ @type = type ## assign class to its own property - why? why not?
28
+ else
29
+ @type = Type.registry[type.to_sym]
30
+ if @type.nil?
31
+ puts "!!!! warn unknown type >#{type}< - no class mapping found; add missing type to Record::Type.registry[]"
32
+ ## todo/fix: raise exception!!!!
33
+ end
34
+ end
35
+ end
36
+
37
+
38
+ def typecast( value ) ## cast (convert) from string value to type (e.g. float, integer, etc.)
39
+ ## todo: add typecast to class itself (monkey patch String/Float etc. - why? why not)
40
+ ## use __typecast or something?
41
+ ## or use a new "wrapper" class Type::String or StringType - why? why not?
42
+
43
+ ## convert string value to (field) type
44
+ if @type == String
45
+ value ## pass through as is
46
+ elsif @type == Float
47
+ ## note: allow/check for nil values - why? why not?
48
+ float = (value.nil? || value.empty?) ? nil : value.to_f
49
+ puts "typecast >#{value}< to float number >#{float}<"
50
+ float
51
+ elsif @type == Integer
52
+ number = (value.nil? || value.empty?) ? nil : value.to_i(10) ## always use base10 for now (e.g. 010 => 10 etc.)
53
+ puts "typecast >#{value}< to integer number >#{number}<"
54
+ number
55
+ else
56
+ ## todo/fix: raise exception about unknow type
57
+ pp @type
58
+ puts "!!!! unknown type >#{@type}< - don't know how to convert/typecast string value >#{value}<"
59
+ value
60
+ end
61
+ end
62
+ end # class Field
63
+
64
+
65
+
66
+ class Type ## todo: use a module - it's just a namespace/module now - why? why not?
67
+
68
+ ## e.g. use Type.registry[:string] = String etc.
69
+ ## note use @@ - there is only one registry
70
+ def self.registry() @@registry ||={} end
71
+
72
+ ## add built-in types:
73
+ registry[:string] = String
74
+ registry[:integer] = Integer ## todo/check: add :number alias for integer? why? why not?
75
+ registry[:float] = Float
76
+ ## todo: add some more
77
+ end # class Type
78
+
79
+
80
+ end # module Record
@@ -5,7 +5,7 @@ module Record
5
5
 
6
6
  MAJOR = 1 ## todo: namespace inside version or something - why? why not??
7
7
  MINOR = 1
8
- PATCH = 0
8
+ PATCH = 1
9
9
  VERSION = [MAJOR,MINOR,PATCH].join('.')
10
10
 
11
11
 
data/test/helper.rb ADDED
@@ -0,0 +1,9 @@
1
+ ## $:.unshift(File.dirname(__FILE__))
2
+
3
+ ## minitest setup
4
+
5
+ require 'minitest/autorun'
6
+
7
+
8
+ ## our own code
9
+ require 'record'
@@ -0,0 +1,165 @@
1
+ # encoding: utf-8
2
+
3
+ ###
4
+ # to run use
5
+ # ruby -I ./lib -I ./test test/test_record.rb
6
+
7
+
8
+ require 'helper'
9
+
10
+ class TestRecord < MiniTest::Test
11
+
12
+ def test_class_style1
13
+ clazz1 = Record.define do
14
+ field :brewery, :string # fix: do NOT use 'Brewery' - name SHOULD be a valid variable name
15
+ field :city, :string
16
+ field :name ## default type is :string
17
+ field :abv, Float ## allow type specified as class
18
+ end
19
+ pp clazz1
20
+ pp clazz1.fields
21
+
22
+ assert true ## assume ok if we get here
23
+ end
24
+
25
+ Beer = Record.define do
26
+ string :brewery # fix: do NOT use 'Brewery' - name SHOULD be a valid variable name
27
+ string :city
28
+ string :name
29
+ float :abv
30
+ end
31
+
32
+ class BeerClassic < Record::Base
33
+ field :brewery
34
+ field :city
35
+ field :name
36
+ field :abv, Float
37
+ end
38
+
39
+
40
+ def test_class_style2
41
+ clazz2 = Record.define do
42
+ string :brewery # fix: do NOT use 'Brewery' - name SHOULD be a valid variable name
43
+ string :city
44
+ string :name
45
+ float :abv
46
+ end
47
+ pp clazz2
48
+ pp clazz2.class.name
49
+ pp clazz2.fields
50
+
51
+ clazz2b = Record.define do |rec| ## try "yield"-style dsl with block.arity == 1
52
+ rec.string :brewery
53
+ rec.string :city
54
+ rec.string :name
55
+ rec.float :abv
56
+ end
57
+ pp clazz2b
58
+ pp clazz2b.class.name
59
+ pp clazz2b.fields
60
+
61
+ assert true ## assume ok if we get here
62
+ end
63
+
64
+
65
+
66
+ def test_classic
67
+ pp BeerClassic.fields
68
+ pp BeerClassic.field_names
69
+ pp BeerClassic.columns ## try fields alias
70
+ pp BeerClassic.column_names ## try field_names alias
71
+
72
+ assert_equal [:brewery, :city, :name, :abv], BeerClassic.field_names
73
+ assert_equal [String, String, String, Float], BeerClassic.field_types
74
+
75
+ beer = BeerClassic.new
76
+ pp beer
77
+ beer.update( abv: 7.0 )
78
+ beer.update( brewery: 'Andechser Klosterbrauerei',
79
+ city: 'Andechs',
80
+ name: 'Doppelbock Dunkel' )
81
+ pp beer
82
+
83
+ assert_equal 7.0, beer.abv
84
+ assert_equal 'Andechser Klosterbrauerei', beer.brewery
85
+ assert_equal 'Andechs', beer.city
86
+ assert_equal 'Doppelbock Dunkel', beer.name
87
+ end
88
+
89
+
90
+ def test_new
91
+ beer = Beer.new
92
+ pp beer
93
+ beer.update( abv: 7.0 )
94
+ beer.update( brewery: 'Andechser Klosterbrauerei',
95
+ city: 'Andechs',
96
+ name: 'Doppelbock Dunkel' )
97
+ pp beer
98
+
99
+ assert_equal 7.0, beer.abv
100
+ assert_equal 'Andechser Klosterbrauerei', beer.brewery
101
+ assert_equal 'Andechs', beer.city
102
+ assert_equal 'Doppelbock Dunkel', beer.name
103
+
104
+
105
+ pp beer.abv
106
+ pp beer.abv = 7.0
107
+ pp beer.abv
108
+ assert_equal 7.0, beer.abv
109
+
110
+ pp beer.parse_abv( '7%' ) ## (auto-)converts/typecasts string to specified type (e.g. float)
111
+ assert_equal 7.0, beer.abv
112
+
113
+ pp beer.name
114
+ pp beer.name = 'Doppelbock Dunkel'
115
+ pp beer.name
116
+ assert_equal 'Doppelbock Dunkel', beer.name
117
+
118
+
119
+ beer2 = Beer.new( brewery: 'Andechser Klosterbrauerei',
120
+ city: 'Andechs',
121
+ name: 'Doppelbock Dunkel' )
122
+ pp beer2
123
+
124
+ assert_nil beer2.abv
125
+ assert_equal 'Andechser Klosterbrauerei', beer2.brewery
126
+ assert_equal 'Andechs', beer2.city
127
+ assert_equal 'Doppelbock Dunkel', beer2.name
128
+ end
129
+
130
+ def test_parse
131
+ values = ['Andechser Klosterbrauerei',
132
+ 'Andechs',
133
+ 'Doppelbock Dunkel',
134
+ '7%']
135
+
136
+ beer = Beer.new
137
+ beer.parse( values )
138
+
139
+ assert_equal values[0], beer.brewery
140
+ assert_equal values[1], beer.city
141
+ assert_equal values[2], beer.name
142
+ assert_equal values[3].to_f, beer.abv
143
+
144
+ assert_equal values[0], beer[0]
145
+ assert_equal values[1], beer[1]
146
+ assert_equal values[2], beer[2]
147
+ assert_equal values[3].to_f, beer[3]
148
+
149
+ assert_equal values[0], beer.values[0]
150
+ assert_equal values[1], beer.values[1]
151
+ assert_equal values[2], beer.values[2]
152
+ assert_equal values[3].to_f, beer.values[3]
153
+
154
+ assert_equal values[0], beer[:brewery]
155
+ assert_equal values[1], beer[:city]
156
+ assert_equal values[2], beer[:name]
157
+ assert_equal values[3].to_f, beer[:abv]
158
+
159
+ assert_equal values[0], beer['Brewery']
160
+ assert_equal values[1], beer['City']
161
+ assert_equal values[2], beer['Name']
162
+ assert_equal values[3].to_f, beer['Abv']
163
+ end
164
+
165
+ end # class TestRecord
@@ -0,0 +1,20 @@
1
+ # encoding: utf-8
2
+
3
+ ###
4
+ # to run use
5
+ # ruby -I ./lib -I ./test test/test_version.rb
6
+
7
+
8
+ require 'helper'
9
+
10
+ class TestVersion < MiniTest::Test
11
+
12
+ def test_version
13
+ pp Record::VERSION
14
+ pp Record.banner
15
+ pp Record.root
16
+
17
+ assert true ## assume ok if we get here
18
+ end
19
+
20
+ end # class TestVersion
metadata CHANGED
@@ -1,7 +1,7 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: record
3
3
  version: !ruby/object:Gem::Version
4
- version: 1.1.0
4
+ version: 1.1.1
5
5
  platform: ruby
6
6
  authors:
7
7
  - Gerald Bauer
@@ -54,7 +54,13 @@ files:
54
54
  - README.md
55
55
  - Rakefile
56
56
  - lib/record.rb
57
+ - lib/record/base.rb
58
+ - lib/record/builder.rb
59
+ - lib/record/field.rb
57
60
  - lib/record/version.rb
61
+ - test/helper.rb
62
+ - test/test_record.rb
63
+ - test/test_version.rb
58
64
  homepage: https://github.com/rubylibs/record
59
65
  licenses:
60
66
  - Public Domain