record 1.1.0 → 1.1.1

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