parametric 0.1.3 → 0.2.0
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +5 -5
- data/README.md +166 -8
- data/lib/parametric/dsl.rb +5 -0
- data/lib/parametric/field.rb +1 -1
- data/lib/parametric/schema.rb +39 -5
- data/lib/parametric/struct.rb +80 -0
- data/lib/parametric/version.rb +1 -1
- data/spec/expand_spec.rb +29 -0
- data/spec/struct_spec.rb +277 -0
- metadata +8 -3
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
|
-
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
2
|
+
SHA256:
|
3
|
+
metadata.gz: f5dfee33e2a3f175505899e0238390e2795e0a862469b851a3a550aef152e8be
|
4
|
+
data.tar.gz: c1225c196aeb662384da471dd31b62ea8e072a90049eecb609976dfa21eec1d6
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 2645e19eda4c75f82f8620fa16cce48fad7b60a14d1749e78a3de05411b55f1daf2ae9e41149467ccd1ccecd613178972d8283ead6a8becdf0c35bae1d4a7123
|
7
|
+
data.tar.gz: 84ecd165ff1355b17f5596f7f7c47a1b2965a2a7c50e6211801400f019aa8d318847ebf68ecbbf2c6c75da6cd8f3bb257707f9e69a4d9b5500ddf82c52733ff2
|
data/README.md
CHANGED
@@ -474,10 +474,10 @@ create_user_schema.structure[:friends].structure[:name].label # => "Friend full
|
|
474
474
|
Note that many field policies add field meta data.
|
475
475
|
|
476
476
|
```ruby
|
477
|
-
create_user_schema.
|
478
|
-
create_user_schema.
|
479
|
-
create_user_schema.
|
480
|
-
create_user_schema.
|
477
|
+
create_user_schema.structure[:name][:type] # => :string
|
478
|
+
create_user_schema.structure[:name][:required] # => true
|
479
|
+
create_user_schema.structure[:status][:options] # => ["published", "unpublished"]
|
480
|
+
create_user_schema.structure[:status][:default] # => "published"
|
481
481
|
```
|
482
482
|
|
483
483
|
## #walk
|
@@ -487,7 +487,7 @@ The `#walk` method can recursively walk a schema definition and extract meta dat
|
|
487
487
|
```ruby
|
488
488
|
schema_documentation = create_user_schema.walk do |field|
|
489
489
|
{type: field.meta_data[:type], label: field.meta_data[:label]}
|
490
|
-
end
|
490
|
+
end.output
|
491
491
|
|
492
492
|
# Returns
|
493
493
|
|
@@ -507,7 +507,7 @@ end
|
|
507
507
|
When passed a _symbol_, it will collect that key from field meta data.
|
508
508
|
|
509
509
|
```ruby
|
510
|
-
schema_labels = create_user_schema.walk(:label)
|
510
|
+
schema_labels = create_user_schema.walk(:label).output
|
511
511
|
|
512
512
|
# returns
|
513
513
|
|
@@ -690,8 +690,7 @@ class CreateUserForm
|
|
690
690
|
attr_reader :errors, :params
|
691
691
|
|
692
692
|
def initialize(payload: {})
|
693
|
-
|
694
|
-
results = self.class.schema.resolve(params)
|
693
|
+
results = self.class.schema.resolve(payload)
|
695
694
|
@errors = results.errors
|
696
695
|
@params = results.output
|
697
696
|
end
|
@@ -743,6 +742,165 @@ class CreateUserForm
|
|
743
742
|
end
|
744
743
|
```
|
745
744
|
|
745
|
+
## Expanding fields dynamically
|
746
|
+
|
747
|
+
Sometimes you don't know the exact field names but you want to allow arbitrary fields depending on a given pattern.
|
748
|
+
|
749
|
+
```ruby
|
750
|
+
# with this payload:
|
751
|
+
# {
|
752
|
+
# title: "A title",
|
753
|
+
# :"custom_attr_Color" => "red",
|
754
|
+
# :"custom_attr_Material" => "leather"
|
755
|
+
# }
|
756
|
+
|
757
|
+
schema = Parametric::Schema.new do
|
758
|
+
field(:title).type(:string).present
|
759
|
+
# here we allow any field starting with /^custom_attr/
|
760
|
+
# this yields a MatchData object to the block
|
761
|
+
# where you can define a Field and validations on the fly
|
762
|
+
# https://ruby-doc.org/core-2.2.0/MatchData.html
|
763
|
+
expand(/^custom_attr_(.+)/) do |match|
|
764
|
+
field(match[1]).type(:string).present
|
765
|
+
end
|
766
|
+
end
|
767
|
+
|
768
|
+
results = schema.resolve({
|
769
|
+
title: "A title",
|
770
|
+
:"custom_attr_Color" => "red",
|
771
|
+
:"custom_attr_Material" => "leather",
|
772
|
+
:"custom_attr_Weight" => "",
|
773
|
+
})
|
774
|
+
|
775
|
+
results.ouput[:Color] # => "red"
|
776
|
+
results.ouput[:Material] # => "leather"
|
777
|
+
results.errors["$.Weight"] # => ["is required and value must be present"]
|
778
|
+
```
|
779
|
+
|
780
|
+
NOTES: dynamically expanded field names are not included in `Schema#structure` metadata, and they are only processes if fields with the given expressions are present in the payload. This means that validations applied to those fields only run if keys are present in the first place.
|
781
|
+
|
782
|
+
## Structs
|
783
|
+
|
784
|
+
Structs turn schema definitions into objects graphs with attribute readers.
|
785
|
+
|
786
|
+
Add optional `Parametrict::Struct` module to define struct-like objects with schema definitions.
|
787
|
+
|
788
|
+
```ruby
|
789
|
+
require 'parametric/struct'
|
790
|
+
|
791
|
+
class User
|
792
|
+
include Parametric::Struct
|
793
|
+
|
794
|
+
schema do
|
795
|
+
field(:name).type(:string).present
|
796
|
+
field(:friends).type(:array).schema do
|
797
|
+
field(:name).type(:string).present
|
798
|
+
field(:age).type(:integer)
|
799
|
+
end
|
800
|
+
end
|
801
|
+
end
|
802
|
+
```
|
803
|
+
|
804
|
+
`User` objects can be instantiated with hash data, which will be coerced and validated as per the schema definition.
|
805
|
+
|
806
|
+
```ruby
|
807
|
+
user = User.new(
|
808
|
+
name: 'Joe',
|
809
|
+
friends: [
|
810
|
+
{name: 'Jane', age: 40},
|
811
|
+
{name: 'John', age: 30},
|
812
|
+
]
|
813
|
+
)
|
814
|
+
|
815
|
+
# properties
|
816
|
+
user.name # => 'Joe'
|
817
|
+
user.friends.first.name # => 'Jane'
|
818
|
+
user.friends.last.age # => 30
|
819
|
+
```
|
820
|
+
|
821
|
+
### Errors
|
822
|
+
|
823
|
+
Both the top-level and nested instances contain error information:
|
824
|
+
|
825
|
+
```ruby
|
826
|
+
user = User.new(
|
827
|
+
name: '', # invalid
|
828
|
+
friends: [
|
829
|
+
# friend name also invalid
|
830
|
+
{name: '', age: 40},
|
831
|
+
]
|
832
|
+
)
|
833
|
+
|
834
|
+
user.valid? # false
|
835
|
+
user.errors['$.name'] # => "is required and must be present"
|
836
|
+
user.errors['$.friends[0].name'] # => "is required and must be present"
|
837
|
+
|
838
|
+
# also access error in nested instances directly
|
839
|
+
user.friends.first.valid? # false
|
840
|
+
user.friends.first.errors['$.name'] # "is required and must be valid"
|
841
|
+
```
|
842
|
+
|
843
|
+
### Nested structs
|
844
|
+
|
845
|
+
You can also pass separate struct classes in a nested schema definition.
|
846
|
+
|
847
|
+
```ruby
|
848
|
+
class Friend
|
849
|
+
include Parametric::Struct
|
850
|
+
|
851
|
+
schema do
|
852
|
+
field(:name).type(:string).present
|
853
|
+
field(:age).type(:integer)
|
854
|
+
end
|
855
|
+
end
|
856
|
+
|
857
|
+
class User
|
858
|
+
include Parametric::Struct
|
859
|
+
|
860
|
+
schema do
|
861
|
+
field(:name).type(:string).present
|
862
|
+
# here we use the Friend class
|
863
|
+
field(:friends).type(:array).schema Friend
|
864
|
+
end
|
865
|
+
end
|
866
|
+
```
|
867
|
+
|
868
|
+
### Inheritance
|
869
|
+
|
870
|
+
Struct subclasses can add to inherited schemas, or override fields defined in the parent.
|
871
|
+
|
872
|
+
```ruby
|
873
|
+
class AdminUser < User
|
874
|
+
# inherits User schema, and can add stuff to its own schema
|
875
|
+
schema do
|
876
|
+
field(:permissions).type(:array)
|
877
|
+
end
|
878
|
+
end
|
879
|
+
```
|
880
|
+
|
881
|
+
### #to_h
|
882
|
+
|
883
|
+
`Struct#to_h` returns the ouput hash, with values coerced and any defaults populated.
|
884
|
+
|
885
|
+
```ruby
|
886
|
+
class User
|
887
|
+
include Parametrict::Struct
|
888
|
+
schema do
|
889
|
+
field(:name).type(:string)
|
890
|
+
field(:age).type(:integer).default(30)
|
891
|
+
end
|
892
|
+
end
|
893
|
+
|
894
|
+
user = User.new(name: "Joe")
|
895
|
+
user.to_h # {name: "Joe", age: 30}
|
896
|
+
```
|
897
|
+
|
898
|
+
### Struct equality
|
899
|
+
|
900
|
+
`Parametric::Struct` implements `#==()` to compare two structs Hash representation (same as `struct1.to_h.eql?(struct2.to_h)`.
|
901
|
+
|
902
|
+
Users can override `#==()` in their own classes to do whatever they need.
|
903
|
+
|
746
904
|
## Installation
|
747
905
|
|
748
906
|
Add this line to your application's Gemfile:
|
data/lib/parametric/dsl.rb
CHANGED
data/lib/parametric/field.rb
CHANGED
data/lib/parametric/schema.rb
CHANGED
@@ -11,6 +11,11 @@ module Parametric
|
|
11
11
|
@definitions << block if block_given?
|
12
12
|
@default_field_policies = []
|
13
13
|
@ignored_field_keys = []
|
14
|
+
@expansions = {}
|
15
|
+
end
|
16
|
+
|
17
|
+
def schema
|
18
|
+
self
|
14
19
|
end
|
15
20
|
|
16
21
|
def fields
|
@@ -80,6 +85,11 @@ module Parametric
|
|
80
85
|
end
|
81
86
|
end
|
82
87
|
|
88
|
+
def expand(exp, &block)
|
89
|
+
expansions[exp] = block
|
90
|
+
self
|
91
|
+
end
|
92
|
+
|
83
93
|
def resolve(payload)
|
84
94
|
context = Context.new
|
85
95
|
output = coerce(payload, nil, context)
|
@@ -112,10 +122,13 @@ module Parametric
|
|
112
122
|
def coerce(val, _, context)
|
113
123
|
if val.is_a?(Array)
|
114
124
|
val.map.with_index{|v, idx|
|
115
|
-
|
125
|
+
subcontext = context.sub(idx)
|
126
|
+
out = coerce_one(v, subcontext)
|
127
|
+
resolve_expansions(v, out, subcontext)
|
116
128
|
}
|
117
129
|
else
|
118
|
-
coerce_one
|
130
|
+
out = coerce_one(val, context)
|
131
|
+
resolve_expansions(val, out, context)
|
119
132
|
end
|
120
133
|
end
|
121
134
|
|
@@ -125,10 +138,10 @@ module Parametric
|
|
125
138
|
|
126
139
|
private
|
127
140
|
|
128
|
-
attr_reader :default_field_policies, :ignored_field_keys
|
141
|
+
attr_reader :default_field_policies, :ignored_field_keys, :expansions
|
129
142
|
|
130
|
-
def coerce_one(val, context)
|
131
|
-
|
143
|
+
def coerce_one(val, context, flds: fields)
|
144
|
+
flds.each_with_object({}) do |(_, field), m|
|
132
145
|
r = field.resolve(val, context.sub(field.key))
|
133
146
|
if r.eligible?
|
134
147
|
m[field.key] = r.value
|
@@ -136,6 +149,27 @@ module Parametric
|
|
136
149
|
end
|
137
150
|
end
|
138
151
|
|
152
|
+
class MatchContext
|
153
|
+
def field(key)
|
154
|
+
Field.new(key.to_sym)
|
155
|
+
end
|
156
|
+
end
|
157
|
+
|
158
|
+
def resolve_expansions(payload, into, context)
|
159
|
+
expansions.each do |exp, block|
|
160
|
+
payload.each do |key, value|
|
161
|
+
if match = exp.match(key.to_s)
|
162
|
+
fld = MatchContext.new.instance_exec(match, &block)
|
163
|
+
if fld
|
164
|
+
into.update(coerce_one({fld.key => value}, context, flds: {fld.key => apply_default_field_policies_to(fld)}))
|
165
|
+
end
|
166
|
+
end
|
167
|
+
end
|
168
|
+
end
|
169
|
+
|
170
|
+
into
|
171
|
+
end
|
172
|
+
|
139
173
|
def apply_default_field_policies_to(field)
|
140
174
|
default_field_policies.reduce(field) {|f, policy_name| f.policy(policy_name) }
|
141
175
|
end
|
@@ -0,0 +1,80 @@
|
|
1
|
+
require 'parametric/dsl'
|
2
|
+
|
3
|
+
module Parametric
|
4
|
+
module Struct
|
5
|
+
def self.included(base)
|
6
|
+
base.send(:include, Parametric::DSL)
|
7
|
+
base.extend ClassMethods
|
8
|
+
end
|
9
|
+
|
10
|
+
def initialize(attrs = {})
|
11
|
+
@_results = self.class.schema.resolve(attrs)
|
12
|
+
@_graph = self.class.build(@_results.output)
|
13
|
+
end
|
14
|
+
|
15
|
+
def valid?
|
16
|
+
!_results.errors.any?
|
17
|
+
end
|
18
|
+
|
19
|
+
def errors
|
20
|
+
_results.errors
|
21
|
+
end
|
22
|
+
|
23
|
+
# returns a shallow copy.
|
24
|
+
def to_h
|
25
|
+
_results.output.clone
|
26
|
+
end
|
27
|
+
|
28
|
+
def ==(other)
|
29
|
+
other.respond_to?(:to_h) && other.to_h.eql?(to_h)
|
30
|
+
end
|
31
|
+
|
32
|
+
def merge(attrs = {})
|
33
|
+
self.class.new(to_h.merge(attrs))
|
34
|
+
end
|
35
|
+
|
36
|
+
private
|
37
|
+
attr_reader :_graph, :_results
|
38
|
+
|
39
|
+
module ClassMethods
|
40
|
+
# this hook is called after schema definition in DSL module
|
41
|
+
def after_define_schema(schema)
|
42
|
+
schema.fields.keys.each do |key|
|
43
|
+
define_method key do
|
44
|
+
_graph[key]
|
45
|
+
end
|
46
|
+
end
|
47
|
+
end
|
48
|
+
|
49
|
+
def build(attrs)
|
50
|
+
attrs.each_with_object({}) do |(k, v), obj|
|
51
|
+
obj[k] = wrap(k, v)
|
52
|
+
end
|
53
|
+
end
|
54
|
+
|
55
|
+
def wrap(key, value)
|
56
|
+
field = schema.fields[key]
|
57
|
+
return value unless field
|
58
|
+
|
59
|
+
case value
|
60
|
+
when Hash
|
61
|
+
# find constructor for field
|
62
|
+
cons = field.meta_data[:schema]
|
63
|
+
if cons.kind_of?(Parametric::Schema)
|
64
|
+
klass = Class.new do
|
65
|
+
include Struct
|
66
|
+
end
|
67
|
+
klass.schema = cons
|
68
|
+
klass.after_define_schema(cons)
|
69
|
+
cons = klass
|
70
|
+
end
|
71
|
+
cons ? cons.new(value) : value.freeze
|
72
|
+
when Array
|
73
|
+
value.map{|v| wrap(key, v) }.freeze
|
74
|
+
else
|
75
|
+
value.freeze
|
76
|
+
end
|
77
|
+
end
|
78
|
+
end
|
79
|
+
end
|
80
|
+
end
|
data/lib/parametric/version.rb
CHANGED
data/spec/expand_spec.rb
ADDED
@@ -0,0 +1,29 @@
|
|
1
|
+
require 'spec_helper'
|
2
|
+
|
3
|
+
describe Parametric::Schema do
|
4
|
+
it "expands fields dynamically" do
|
5
|
+
schema = described_class.new do
|
6
|
+
field(:title).type(:string).present
|
7
|
+
expand(/^attr_(.+)/) do |match|
|
8
|
+
field(match[1]).type(:string)
|
9
|
+
end
|
10
|
+
expand(/^validate_(.+)/) do |match|
|
11
|
+
field(match[1]).type(:string).present
|
12
|
+
end
|
13
|
+
end
|
14
|
+
|
15
|
+
out = schema.resolve({
|
16
|
+
title: "foo",
|
17
|
+
:"attr_Attribute 1" => "attr 1",
|
18
|
+
:"attr_Attribute 2" => "attr 2",
|
19
|
+
:"validate_valid_attr" => "valid",
|
20
|
+
:"validate_invalid_attr" => "",
|
21
|
+
})
|
22
|
+
|
23
|
+
expect(out.output[:title]).to eq 'foo'
|
24
|
+
expect(out.output[:"Attribute 1"]).to eq 'attr 1'
|
25
|
+
expect(out.output[:"Attribute 2"]).to eq 'attr 2'
|
26
|
+
|
27
|
+
expect(out.errors['$.invalid_attr']).to eq ['is required and value must be present']
|
28
|
+
end
|
29
|
+
end
|
data/spec/struct_spec.rb
ADDED
@@ -0,0 +1,277 @@
|
|
1
|
+
require 'spec_helper'
|
2
|
+
require 'parametric/struct'
|
3
|
+
|
4
|
+
describe Parametric::Struct do
|
5
|
+
it "works" do
|
6
|
+
friend_class = Class.new do
|
7
|
+
include Parametric::Struct
|
8
|
+
|
9
|
+
schema do
|
10
|
+
field(:name).type(:string).present
|
11
|
+
field(:age).type(:integer)
|
12
|
+
end
|
13
|
+
end
|
14
|
+
|
15
|
+
klass = Class.new do
|
16
|
+
include Parametric::Struct
|
17
|
+
|
18
|
+
schema do
|
19
|
+
field(:title).type(:string).present
|
20
|
+
field(:friends).type(:array).default([]).schema friend_class
|
21
|
+
end
|
22
|
+
end
|
23
|
+
|
24
|
+
new_instance = klass.new
|
25
|
+
expect(new_instance.title).to eq ''
|
26
|
+
expect(new_instance.friends).to eq []
|
27
|
+
expect(new_instance.valid?).to be false
|
28
|
+
expect(new_instance.errors['$.title']).not_to be_nil
|
29
|
+
|
30
|
+
instance = klass.new({
|
31
|
+
title: 'foo',
|
32
|
+
friends: [
|
33
|
+
{name: 'Ismael', age: 40},
|
34
|
+
{name: 'Joe', age: 39},
|
35
|
+
]
|
36
|
+
})
|
37
|
+
|
38
|
+
expect(instance.title).to eq 'foo'
|
39
|
+
expect(instance.friends.size).to eq 2
|
40
|
+
expect(instance.friends.first.name).to eq 'Ismael'
|
41
|
+
expect(instance.friends.first).to be_a friend_class
|
42
|
+
|
43
|
+
invalid_instance = klass.new({
|
44
|
+
friends: [
|
45
|
+
{name: 'Ismael', age: 40},
|
46
|
+
{age: 39},
|
47
|
+
]
|
48
|
+
})
|
49
|
+
|
50
|
+
expect(invalid_instance.valid?).to be false
|
51
|
+
expect(invalid_instance.errors['$.title']).not_to be_nil
|
52
|
+
expect(invalid_instance.errors['$.friends[1].name']).not_to be_nil
|
53
|
+
expect(invalid_instance.friends[1].errors['$.name']).not_to be_nil
|
54
|
+
end
|
55
|
+
|
56
|
+
it "is inmutable by default" do
|
57
|
+
klass = Class.new do
|
58
|
+
include Parametric::Struct
|
59
|
+
|
60
|
+
schema do
|
61
|
+
field(:title).type(:string).present
|
62
|
+
field(:friends).type(:array).default([])
|
63
|
+
field(:friend).type(:object)
|
64
|
+
end
|
65
|
+
end
|
66
|
+
|
67
|
+
instance = klass.new
|
68
|
+
expect {
|
69
|
+
instance.title = "foo"
|
70
|
+
}.to raise_error NoMethodError
|
71
|
+
|
72
|
+
expect {
|
73
|
+
instance.friends << 1
|
74
|
+
}.to raise_error RuntimeError
|
75
|
+
end
|
76
|
+
|
77
|
+
it "works with anonymous nested schemas" do
|
78
|
+
klass = Class.new do
|
79
|
+
include Parametric::Struct
|
80
|
+
|
81
|
+
schema do
|
82
|
+
field(:title).type(:string).present
|
83
|
+
field(:friends).type(:array).schema do
|
84
|
+
field(:age).type(:integer)
|
85
|
+
end
|
86
|
+
end
|
87
|
+
end
|
88
|
+
|
89
|
+
instance = klass.new({
|
90
|
+
title: 'foo',
|
91
|
+
friends: [
|
92
|
+
{age: 10},
|
93
|
+
{age: 39},
|
94
|
+
]
|
95
|
+
})
|
96
|
+
|
97
|
+
expect(instance.title).to eq 'foo'
|
98
|
+
expect(instance.friends.size).to eq 2
|
99
|
+
expect(instance.friends.first.age).to eq 10
|
100
|
+
end
|
101
|
+
|
102
|
+
it "wraps regular schemas in structs" do
|
103
|
+
friend_schema = Parametric::Schema.new do
|
104
|
+
field(:name)
|
105
|
+
end
|
106
|
+
|
107
|
+
klass = Class.new do
|
108
|
+
include Parametric::Struct
|
109
|
+
|
110
|
+
schema do
|
111
|
+
field(:title).type(:string).present
|
112
|
+
field(:friends).type(:array).schema friend_schema
|
113
|
+
end
|
114
|
+
end
|
115
|
+
|
116
|
+
instance = klass.new({
|
117
|
+
title: 'foo',
|
118
|
+
friends: [{name: 'Ismael'}]
|
119
|
+
})
|
120
|
+
|
121
|
+
expect(instance.friends.first.name).to eq 'Ismael'
|
122
|
+
end
|
123
|
+
|
124
|
+
it "#to_h" do
|
125
|
+
klass = Class.new do
|
126
|
+
include Parametric::Struct
|
127
|
+
|
128
|
+
schema do
|
129
|
+
field(:title).type(:string).present
|
130
|
+
field(:friends).type(:array).schema do
|
131
|
+
field(:name).type(:string)
|
132
|
+
field(:age).type(:integer).default(20)
|
133
|
+
end
|
134
|
+
end
|
135
|
+
end
|
136
|
+
|
137
|
+
instance = klass.new({
|
138
|
+
title: 'foo',
|
139
|
+
friends: [
|
140
|
+
{name: 'Jane'},
|
141
|
+
{name: 'Joe', age: '39'},
|
142
|
+
]
|
143
|
+
})
|
144
|
+
|
145
|
+
expect(instance.to_h).to eq({
|
146
|
+
title: 'foo',
|
147
|
+
friends: [
|
148
|
+
{name: 'Jane', age: 20},
|
149
|
+
{name: 'Joe', age: 39},
|
150
|
+
]
|
151
|
+
})
|
152
|
+
|
153
|
+
new_instance = klass.new(instance.to_h)
|
154
|
+
expect(new_instance.title).to eq 'foo'
|
155
|
+
|
156
|
+
# it returns a copy so we can't break things!
|
157
|
+
data = new_instance.to_h
|
158
|
+
data[:title] = 'nope'
|
159
|
+
expect(new_instance.to_h[:title]).to eq 'foo'
|
160
|
+
end
|
161
|
+
|
162
|
+
it "works with inheritance" do
|
163
|
+
klass = Class.new do
|
164
|
+
include Parametric::Struct
|
165
|
+
|
166
|
+
schema do
|
167
|
+
field(:title).type(:string).present
|
168
|
+
field(:friends).type(:array).schema do
|
169
|
+
field(:name).type(:string)
|
170
|
+
field(:age).type(:integer).default(20)
|
171
|
+
end
|
172
|
+
end
|
173
|
+
end
|
174
|
+
|
175
|
+
subclass = Class.new(klass) do
|
176
|
+
schema do
|
177
|
+
field(:email)
|
178
|
+
end
|
179
|
+
end
|
180
|
+
|
181
|
+
instance = subclass.new(
|
182
|
+
title: 'foo',
|
183
|
+
email: 'email@me.com',
|
184
|
+
friends: [
|
185
|
+
{name: 'Jane', age: 20},
|
186
|
+
{name: 'Joe', age: 39},
|
187
|
+
]
|
188
|
+
)
|
189
|
+
|
190
|
+
expect(instance.title).to eq 'foo'
|
191
|
+
expect(instance.email).to eq 'email@me.com'
|
192
|
+
expect(instance.friends.size).to eq 2
|
193
|
+
end
|
194
|
+
|
195
|
+
it "implements deep struct equality" do
|
196
|
+
klass = Class.new do
|
197
|
+
include Parametric::Struct
|
198
|
+
|
199
|
+
schema do
|
200
|
+
field(:title).type(:string).present
|
201
|
+
field(:friends).type(:array).schema do
|
202
|
+
field(:age).type(:integer)
|
203
|
+
end
|
204
|
+
end
|
205
|
+
end
|
206
|
+
|
207
|
+
s1 = klass.new({
|
208
|
+
title: 'foo',
|
209
|
+
friends: [
|
210
|
+
{age: 10},
|
211
|
+
{age: 39},
|
212
|
+
]
|
213
|
+
})
|
214
|
+
|
215
|
+
|
216
|
+
s2 = klass.new({
|
217
|
+
title: 'foo',
|
218
|
+
friends: [
|
219
|
+
{age: 10},
|
220
|
+
{age: 39},
|
221
|
+
]
|
222
|
+
})
|
223
|
+
|
224
|
+
s3 = klass.new({
|
225
|
+
title: 'foo',
|
226
|
+
friends: [
|
227
|
+
{age: 11},
|
228
|
+
{age: 39},
|
229
|
+
]
|
230
|
+
})
|
231
|
+
|
232
|
+
s4 = klass.new({
|
233
|
+
title: 'bar',
|
234
|
+
friends: [
|
235
|
+
{age: 10},
|
236
|
+
{age: 39},
|
237
|
+
]
|
238
|
+
})
|
239
|
+
|
240
|
+
expect(s1 == s2).to be true
|
241
|
+
expect(s1 == s3).to be false
|
242
|
+
expect(s1 == s4).to be false
|
243
|
+
end
|
244
|
+
|
245
|
+
it "#merge returns a new instance" do
|
246
|
+
klass = Class.new do
|
247
|
+
include Parametric::Struct
|
248
|
+
|
249
|
+
schema do
|
250
|
+
field(:title).type(:string).present
|
251
|
+
field(:desc)
|
252
|
+
field(:friends).type(:array).schema do
|
253
|
+
field(:name).type(:string)
|
254
|
+
end
|
255
|
+
end
|
256
|
+
end
|
257
|
+
|
258
|
+
original = klass.new(
|
259
|
+
title: 'foo',
|
260
|
+
desc: 'no change',
|
261
|
+
friends: [{name: 'joe'}]
|
262
|
+
)
|
263
|
+
|
264
|
+
copy = original.merge(
|
265
|
+
title: 'bar',
|
266
|
+
friends: [{name: 'jane'}]
|
267
|
+
)
|
268
|
+
|
269
|
+
expect(original.title).to eq 'foo'
|
270
|
+
expect(original.desc).to eq 'no change'
|
271
|
+
expect(original.friends.first.name).to eq 'joe'
|
272
|
+
|
273
|
+
expect(copy.title).to eq 'bar'
|
274
|
+
expect(copy.desc).to eq 'no change'
|
275
|
+
expect(copy.friends.first.name).to eq 'jane'
|
276
|
+
end
|
277
|
+
end
|
metadata
CHANGED
@@ -1,14 +1,14 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: parametric
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 0.
|
4
|
+
version: 0.2.0
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- Ismael Celis
|
8
8
|
autorequire:
|
9
9
|
bindir: bin
|
10
10
|
cert_chain: []
|
11
|
-
date:
|
11
|
+
date: 2018-08-06 00:00:00.000000000 Z
|
12
12
|
dependencies:
|
13
13
|
- !ruby/object:Gem::Dependency
|
14
14
|
name: bundler
|
@@ -96,14 +96,17 @@ files:
|
|
96
96
|
- lib/parametric/registry.rb
|
97
97
|
- lib/parametric/results.rb
|
98
98
|
- lib/parametric/schema.rb
|
99
|
+
- lib/parametric/struct.rb
|
99
100
|
- lib/parametric/version.rb
|
100
101
|
- parametric.gemspec
|
101
102
|
- spec/dsl_spec.rb
|
103
|
+
- spec/expand_spec.rb
|
102
104
|
- spec/field_spec.rb
|
103
105
|
- spec/policies_spec.rb
|
104
106
|
- spec/schema_spec.rb
|
105
107
|
- spec/schema_walk_spec.rb
|
106
108
|
- spec/spec_helper.rb
|
109
|
+
- spec/struct_spec.rb
|
107
110
|
- spec/validators_spec.rb
|
108
111
|
homepage: ''
|
109
112
|
licenses:
|
@@ -125,16 +128,18 @@ required_rubygems_version: !ruby/object:Gem::Requirement
|
|
125
128
|
version: '0'
|
126
129
|
requirements: []
|
127
130
|
rubyforge_project:
|
128
|
-
rubygems_version: 2.
|
131
|
+
rubygems_version: 2.7.6
|
129
132
|
signing_key:
|
130
133
|
specification_version: 4
|
131
134
|
summary: DSL for declaring allowed parameters with options, regexp patern and default
|
132
135
|
values.
|
133
136
|
test_files:
|
134
137
|
- spec/dsl_spec.rb
|
138
|
+
- spec/expand_spec.rb
|
135
139
|
- spec/field_spec.rb
|
136
140
|
- spec/policies_spec.rb
|
137
141
|
- spec/schema_spec.rb
|
138
142
|
- spec/schema_walk_spec.rb
|
139
143
|
- spec/spec_helper.rb
|
144
|
+
- spec/struct_spec.rb
|
140
145
|
- spec/validators_spec.rb
|