records 0.0.1 → 1.0.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: a634c1c166a9375df4d42da8a5b0c4e0fe63a0c7
4
- data.tar.gz: aedd9d2c2d0ba064d6fd7292dc6456bd35981c74
3
+ metadata.gz: 13d06e265fb13c8800ea2c98f699a839bef77a8e
4
+ data.tar.gz: 530dcd1156f9e7146bb3ff157db9159f4aa8d72d
5
5
  SHA512:
6
- metadata.gz: 36b9c96f91005b5b94ba48ef22d6e7f73fa3276206c03c3238d27a6377241434d16a16225b00b95c68844f93327b1000be8b47cc44c907f35200dd5c23780437
7
- data.tar.gz: a13e7eabfc946b8a3db5c3a070d6bb1df9579de67c5be4cb78e1fb5165bcb2014d51641f7c0e6a5a9157d9e1974f00cf1887728edb0d12baa4b23253a48bc101
6
+ metadata.gz: eafaa2aa7bdcb10b0d4138337e11157d5afc035569d4829c81dadac1f1e20a2c08801f8811208f0e9108c3b531a1d0af436ac2aea49a56fc9c21821406c703ea
7
+ data.tar.gz: e7ae023c0bf8dbdf08b85472e7eb64145973e4a79662d8b4c03c51ce2e4179066613f67e0291acf40062c5df98afe263c4d3052b84d5a34b64ddcf86d0f0ee2d
@@ -4,6 +4,7 @@ Manifest.txt
4
4
  README.md
5
5
  Rakefile
6
6
  lib/records.rb
7
+ lib/records/record.rb
7
8
  lib/records/version.rb
8
9
  test/helper.rb
9
10
  test/test_account.rb
data/README.md CHANGED
@@ -1,7 +1,7 @@
1
1
 
2
2
  # Records - Frozen / Immutable Structs with Copy on Updates
3
3
 
4
- records gem / library - frozen / immutable (frozen) structs with copy on updates
4
+ records gem / library - frozen / immutable structs with copy on updates
5
5
 
6
6
 
7
7
  * home :: [github.com/s6ruby/records](https://github.com/s6ruby/records)
@@ -12,7 +12,94 @@ records gem / library - frozen / immutable (frozen) structs with copy on updates
12
12
 
13
13
  ## Usage
14
14
 
15
- To be done
15
+ Use `Record.new` like `Struct.new` to build / create a new frozen / immutable
16
+ record class. Example:
17
+
18
+ ``` ruby
19
+
20
+ Record.new( :Account,
21
+ balance: Integer,
22
+ allowances: Hash )
23
+
24
+ # -or-
25
+
26
+ Record.new :Account,
27
+ balance: Integer,
28
+ allowances: Hash
29
+
30
+ # -or-
31
+
32
+ Record.new :Account, { balance: Integer,
33
+ allowances: Hash }
34
+
35
+ # -or-
36
+
37
+ class Account < Record::Base
38
+ field :balance, Integer
39
+ field :allowances, Hash
40
+ end
41
+ ```
42
+
43
+
44
+ And use the new record class like:
45
+
46
+ ``` ruby
47
+ account1a = Account.new( 1, {} )
48
+ account1a.frozen? #=> true
49
+ account1a.values.frozen? #=> true
50
+ account1a.balance #=> 1
51
+ account1a.allowances #=> {}
52
+ account1a.values #=> [1, {}]
53
+
54
+ Account.keys #=> [:balance, :allowances]
55
+ Account.fields #=> [<Field @key=:balance, @index=0, @type=Integer>,
56
+ # <Field @key=:allowances, @index=1, @type=Hash>]
57
+ Account.index( :balance ) #=> 0
58
+ Account.index( :allowances ) #=> 1
59
+ ```
60
+
61
+ Note: The `update` method (or the `<<` alias)
62
+ ALWAYS returns a new record.
63
+
64
+ ``` ruby
65
+ account1a.update( balance: 20 ) #=> [20, {}]
66
+ account1a.update( balance: 30 ) #=> [30, {}]
67
+ account1a.update( { balance: 30 } ) #=> [30, {}]
68
+
69
+ account1a << { balance: 20 } #=> [20, {}]
70
+ account1a << { balance: 30 } #=> [30, {}]
71
+
72
+ account1b = account1a.update( balance: 40, allowances: { 'Alice': 20 } )
73
+ account1b.balance #=> 40
74
+ account1b.allowances #=> { 'Alice': 20 }
75
+ account1b.values #=> [40, { 'Alice': 20 } ]
76
+ # ...
77
+ ```
78
+
79
+ And so on and so forth.
80
+
81
+
82
+ ## Bonus - Record Update Language Syntax Pragmas - `{...}` and `={...}`
83
+
84
+ Using the Record Update Pragma. Lets you
85
+
86
+ ``` ruby
87
+ account1a {... balance: 20 } #=> [20, {}]
88
+ account1a {... balance: 30 } #=> [30, {}]
89
+
90
+ account1a = {... balance: 40, allowances: { 'Alice': 20 }}
91
+ ```
92
+
93
+ turn into:
94
+
95
+ ``` ruby
96
+ account1a.update( balance: 20 ) #=> [20, {}]
97
+ account1a.update( balance: 30 ) #=> [30, {}]
98
+
99
+ account1a = account1a.update( balance: 40, allowances: { 'Alice': 20 } )
100
+ ```
101
+
102
+ See [Language Syntax Pragmas - Let's Evolve Ruby by Experimenting in a Pragma(tic) Way](https://github.com/s6ruby/pragmas) for more.
16
103
 
17
104
 
18
105
  ## License
data/Rakefile CHANGED
@@ -5,7 +5,7 @@ Hoe.spec 'records' do
5
5
 
6
6
  self.version = Records::VERSION
7
7
 
8
- self.summary = "records gem / library - frozen / immutable (frozen) structs with copy on updates"
8
+ self.summary = "records - frozen / immutable structs with copy on updates"
9
9
  self.description = summary
10
10
 
11
11
  self.urls = ['https://github.com/s6ruby/records']
@@ -4,7 +4,7 @@ require 'pp'
4
4
 
5
5
  ## our own code
6
6
  require 'records/version' # note: let version always go first
7
-
7
+ require 'records/record'
8
8
 
9
9
 
10
10
 
@@ -0,0 +1,132 @@
1
+ # encoding: utf-8
2
+
3
+
4
+
5
+ class Record
6
+
7
+ class Type; end
8
+ Types = Type # note Types is an alias for Type
9
+
10
+
11
+
12
+ class Field
13
+ attr_reader :key, :type
14
+ attr_reader :index ## note: zero-based position index (0,1,2,3,...)
15
+
16
+ def initialize( key, index, type )
17
+ @key = key.to_sym ## note: always symbol-ify (to_sym) key
18
+ @index = index
19
+ @type = type
20
+ end
21
+ end # class Field
22
+
23
+
24
+
25
+ class Base < Record
26
+
27
+ def self.fields ## note: use class instance variable (@fields and NOT @@fields)!!!! (derived classes get its own copy!!!)
28
+ @fields ||= []
29
+ end
30
+
31
+ def self.keys
32
+ @keys ||= fields.map {|field| field.key }.freeze
33
+ end
34
+
35
+ def self.index( key ) ## indef of key (0,1,2,etc.)
36
+ ## note: returns nil now for unknown keys
37
+ ## use/raise IndexError or something - why? why not?
38
+ @index_by_key ||= Hash[ keys.zip( (0...fields.size).to_a ) ].freeze
39
+ @index_by_key[key]
40
+ end
41
+
42
+
43
+
44
+ def self.field( key, type )
45
+ index = fields.size ## auto-calc num(ber) / position index - always gets added at the end
46
+ field = Field.new( key, index, type )
47
+ fields << field
48
+
49
+ define_field( field ) ## auto-add getter,setter,parse/typecast
50
+ end
51
+
52
+ def self.define_field( field )
53
+ key = field.key ## note: always assumes a "cleaned-up" (symbol) name
54
+ index = field.index
55
+
56
+ define_method( key ) do
57
+ instance_variable_get( "@values" )[index]
58
+ end
59
+ end
60
+
61
+ ## note: "skip" overloaded new Record.new (and use old_new version)
62
+ def self.new( *args ) old_new( *args ); end
63
+
64
+
65
+
66
+ attr_reader :values
67
+
68
+ def initialize( *args )
69
+ #####
70
+ ## todo/fix: add allow keyword init too
71
+ ### note:
72
+ ### if init( 1, {} ) assumes last {} is a kwargs!!!!!
73
+ ## and NOT a "plain" arg in args!!!
74
+
75
+ ## puts "[#{self.class.name}] Record::Base.initialize:"
76
+ ## pp args
77
+
78
+ ##
79
+ ## fix/todo: check that number of args are equal fields.size !!!
80
+ ## check types too :-)
81
+
82
+ @values = args
83
+ @values.freeze
84
+ self.freeze ## freeze self too - why? why not?
85
+ self
86
+ end
87
+
88
+
89
+ def update( **kwargs )
90
+ new_values = @values.dup ## note: use dup NOT clone (will "undo" frozen state?)
91
+ kwargs.each do |key,value|
92
+ index = self.class.index( key )
93
+ new_values[ index ] = value
94
+ end
95
+ self.class.new( *new_values )
96
+ end
97
+
98
+ ## "convenience" shortcut for update e.g.
99
+ ## << { balance: 5 }
100
+ ## equals
101
+ ## .update( balance: 5 )
102
+ def <<( hash ) update( hash ); end
103
+
104
+
105
+ ###
106
+ ## note: compare by value for now (and NOT object id)
107
+ def ==(other)
108
+ if other.instance_of?( self.class )
109
+ values == other.values
110
+ else
111
+ false
112
+ end
113
+ end
114
+ alias_method :eql?, :==
115
+
116
+ end # class Base
117
+
118
+
119
+ def self.build_class( class_name, **attributes )
120
+ klass = Class.new( Base )
121
+ attributes.each do |key, type|
122
+ klass.field( key, type )
123
+ end
124
+
125
+ Type.const_set( class_name, klass ) ## returns klass (plus sets global constant class name)
126
+ end
127
+
128
+ class << self
129
+ alias_method :old_new, :new # note: store "old" orginal version of new
130
+ alias_method :new, :build_class # replace original version with create
131
+ end
132
+ end # class Record
@@ -8,9 +8,9 @@
8
8
 
9
9
  module Records
10
10
 
11
- MAJOR = 0
11
+ MAJOR = 1
12
12
  MINOR = 0
13
- PATCH = 1
13
+ PATCH = 0
14
14
  VERSION = [MAJOR,MINOR,PATCH].join('.')
15
15
 
16
16
  def self.version
@@ -10,4 +10,111 @@ require 'helper'
10
10
 
11
11
  class TestAccount < MiniTest::Test
12
12
 
13
+ Record.build_class( :Account,
14
+ balance: Integer,
15
+ allowances: Hash )
16
+ Account = Record::Type::Account
17
+
18
+
19
+ Record.new( :Account2,
20
+ balance: Integer,
21
+ allowances: Hash )
22
+ Account2 = Record::Type::Account2
23
+
24
+
25
+ class Account3 < Record::Base
26
+ field :balance, Integer
27
+ field :allowances, Hash
28
+ end
29
+
30
+
31
+
32
+ def test_account
33
+ pp Record::Type::Account
34
+ pp Account.ancestors
35
+
36
+ puts "Record::Type.constants:"
37
+ pp Record::Type.constants
38
+
39
+ account1a = Account.new( 1, {} )
40
+ assert_equal Account.new( 1, {} ), account1a
41
+ assert_equal true, account1a.frozen?
42
+ assert_equal true, account1a.values.frozen?
43
+ assert_equal 1, account1a.balance
44
+ assert_equal Hash({}), account1a.allowances
45
+
46
+
47
+ assert_equal Account.new( 20, {} ), account1a.update( balance: 20 )
48
+ assert_equal Account.new( 30, {} ), account1a.update( balance: 30 )
49
+ assert_equal Account.new( 30, {} ), account1a.update( { balance: 30 } )
50
+
51
+ assert_equal Account.new( 20, {} ), account1a << { balance: 20 }
52
+ assert_equal Account.new( 30, {} ), account1a << { balance: 30 }
53
+ assert_equal Account.new( 30, {} ), account1a << Hash( { balance: 30 } )
54
+ end
55
+
56
+
57
+ def test_account2
58
+ pp Record::Type::Account2
59
+ pp Account2.ancestors
60
+
61
+ puts "Record::Type.constants:"
62
+ pp Record::Type.constants
63
+
64
+ assert_equal [:balance, :allowances], Account2.keys
65
+ # assert_equal [Record::Field.new( :balance, 0, Integer),
66
+ # Record::Field.new( :allowances, 1, Hash )
67
+ # ], Account2.fields
68
+
69
+ assert_equal 0, Account2.index( :balance )
70
+ assert_equal 1, Account2.index( :allowances )
71
+
72
+ account1a = Account2.new( 1, {} )
73
+ assert_equal Account2.new( 1, {} ), account1a
74
+ assert_equal [1, {}], account1a.values
75
+
76
+ assert_equal Account2.new( 20, {} ), account1a.update( balance: 20 )
77
+ assert_equal Account2.new( 30, {} ), account1a.update( balance: 30 )
78
+ assert_equal [20, {}], account1a.update( balance: 20 ).values
79
+ assert_equal [30, {}], account1a.update( balance: 30 ).values
80
+
81
+
82
+ account1b = account1a.update( balance: 10, allowances: { '0xaa': 20 } )
83
+ assert_equal Account2.new( 10, { '0xaa': 20 } ), account1b
84
+ assert_equal Hash({ '0xaa': 20 }), account1b.allowances
85
+ end
86
+
87
+
88
+ def test_account3
89
+ pp Account3
90
+ pp Account3.ancestors
91
+
92
+ puts "Record::Type.constants:"
93
+ pp Record::Type.constants
94
+
95
+ account1a = Account3.new( 1, {} )
96
+ assert_equal Account3.new( 1, {} ), account1a
97
+ assert_equal true, account1a.is_a?( Record )
98
+ assert_equal true, account1a.is_a?( Record::Base )
99
+
100
+
101
+ assert_equal Account3.new( 20, {} ), account1a.update( balance: 20 )
102
+ assert_equal Account3.new( 30, {} ), account1a.update( balance: 30 )
103
+ assert_equal [30, {}], account1a.update( balance: 30 ).values
104
+
105
+ account1b = account1a.update( balance: 10 )
106
+ assert_equal Account3.new( 10, {} ), account1b
107
+ assert_equal 10, account1b.balance
108
+ assert_equal Hash({}), account1b.allowances
109
+ assert_equal [10, {}], account1b.values
110
+
111
+ account1b = account1a.update( balance: 10, allowances: { '0xaa': 20 } )
112
+ assert_equal Hash({'0xaa': 20 }), account1b.allowances
113
+ assert_equal [10, {'0xaa': 20 }], account1b.values
114
+
115
+ account2 = Account3.new( 2, {} )
116
+ assert_equal Account3.new( 2, {} ), account2
117
+ assert_equal [2, {}], account2.values
118
+ end
119
+
13
120
  end # class TestAccount
metadata CHANGED
@@ -1,7 +1,7 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: records
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.0.1
4
+ version: 1.0.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - Gerald Bauer
@@ -38,8 +38,7 @@ dependencies:
38
38
  - - "~>"
39
39
  - !ruby/object:Gem::Version
40
40
  version: '3.16'
41
- description: records gem / library - frozen / immutable (frozen) structs with copy
42
- on updates
41
+ description: records - frozen / immutable structs with copy on updates
43
42
  email: wwwmake@googlegroups.com
44
43
  executables: []
45
44
  extensions: []
@@ -55,6 +54,7 @@ files:
55
54
  - README.md
56
55
  - Rakefile
57
56
  - lib/records.rb
57
+ - lib/records/record.rb
58
58
  - lib/records/version.rb
59
59
  - test/helper.rb
60
60
  - test/test_account.rb
@@ -83,6 +83,5 @@ rubyforge_project:
83
83
  rubygems_version: 2.5.2
84
84
  signing_key:
85
85
  specification_version: 4
86
- summary: records gem / library - frozen / immutable (frozen) structs with copy on
87
- updates
86
+ summary: records - frozen / immutable structs with copy on updates
88
87
  test_files: []