records 0.0.1 → 1.0.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: 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: []