parametric 0.2.9 → 0.2.14

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
  SHA256:
3
- metadata.gz: 3e5e99f35e7a900299c8ca79bf1edecc57a73209c8fab7c292ef5fff11f1e467
4
- data.tar.gz: a28ad622475c98957079ee5a000c02f91b1df05c6f3fd9492bcdfd10a6751edc
3
+ metadata.gz: 40164472fa5f68d604b2f20b6823b4b7c0ca81136c46826705d3a3a4b2e903f2
4
+ data.tar.gz: 1bce90d98794e1cc1a169bea9ff0b6d606486a9a2e33b95b0e461cfaae567c63
5
5
  SHA512:
6
- metadata.gz: 86bcd5fcd359872f8eab2b23206bd359b13ab1363d5b7d29eb8ef9899ed421605d3be10cbf4841d2791eeecf46de8292e63d97180c799ae2febb0c955b054c92
7
- data.tar.gz: b767db9489841a70489812bd51be1bb167de8a87b9c91a4f21cb82201d7023ed5e0a05747d853332f936bb0d05f2e3be44d6ed64c6bd24820fb19da1902c708f
6
+ metadata.gz: 0b93f931277e786ea4f6a39d2db2a8152be14a9887c4a31bfb54a38c5127c85bf43b6e35e442b4b9cc40531879d5638ed234b4e62102cffb23c5b2bf12552c6e
7
+ data.tar.gz: ca54de8f1cc1aaf4b0cb4805197e634ab07ecabf31cab56a54e3fca330312c3b247a984d5efabef9350729008e1f319730a74b1b068f6685eb6f9c74ddda71aa
data/Gemfile CHANGED
@@ -2,3 +2,7 @@ source 'https://rubygems.org'
2
2
 
3
3
  # Specify your gem's dependencies in parametric.gemspec
4
4
  gemspec
5
+
6
+ group :development do
7
+ gem 'benchmark-ips'
8
+ end
data/README.md CHANGED
@@ -849,6 +849,130 @@ results.errors["$.Weight"] # => ["is required and value must be present"]
849
849
 
850
850
  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.
851
851
 
852
+ ## Before and after resolve hooks
853
+
854
+ `Schema#before_resolve` can be used to register blocks to modify the entire input payload _before_ individual fields are validated and coerced.
855
+ This can be useful when you need to pre-populate fields relative to other fields' values, or fetch extra data from other sources.
856
+
857
+ ```ruby
858
+ # This example computes the value of the :slug field based on :name
859
+ schema = Parametric::Schema.new do
860
+ # Note1: These blocks run before field validations, so :name might be blank or invalid at this point.
861
+ # Note2: Before hooks _must_ return a payload hash.
862
+ before_resolve do |payload, context|
863
+ payload.merge(
864
+ slug: payload[:name].to_s.downcase.gsub(/\s+/, '-')
865
+ )
866
+ end
867
+
868
+ # You still need to define the fields you want
869
+ field(:name).type(:string).present
870
+ field(:slug).type(:string).present
871
+ end
872
+
873
+ result = schema.resolve( name: 'Joe Bloggs' )
874
+ result.output # => { name: 'Joe Bloggs', slug: 'joe-bloggs' }
875
+ ```
876
+
877
+ Before hooks can be added to nested schemas, too:
878
+
879
+ ```ruby
880
+ schema = Parametric::Schema.new do
881
+ field(:friends).type(:array).schema do
882
+ before_resolve do |friend_payload, context|
883
+ friend_payload.merge(title: "Mr/Ms #{friend_payload[:name]}")
884
+ end
885
+
886
+ field(:name).type(:string)
887
+ field(:title).type(:string)
888
+ end
889
+ end
890
+ ```
891
+
892
+ You can use inline blocks, but anything that responds to `#call(payload, context)` will work, too:
893
+
894
+ ```ruby
895
+ class SlugMaker
896
+ def initialize(slug_field, from:)
897
+ @slug_field, @from = slug_field, from
898
+ end
899
+
900
+ def call(payload, context)
901
+ payload.merge(
902
+ @slug_field => payload[@from].to_s.downcase.gsub(/\s+/, '-')
903
+ )
904
+ end
905
+ end
906
+
907
+ schema = Parametric::Schema.new do
908
+ before_resolve SlugMaker.new(:slug, from: :name)
909
+
910
+ field(:name).type(:string)
911
+ field(:slug).type(:slug)
912
+ end
913
+ ```
914
+
915
+ The `context` argument can be used to add custom validation errors in a before hook block.
916
+
917
+ ```ruby
918
+ schema = Parametric::Schema.new do
919
+ before_resolve do |payload, context|
920
+ # validate that there's no duplicate friend names
921
+ friends = payload[:friends] || []
922
+ if friends.any? && friends.map{ |fr| fr[:name] }.uniq.size < friends.size
923
+ context.add_error 'friend names must be unique'
924
+ end
925
+
926
+ # don't forget to return the payload
927
+ payload
928
+ end
929
+
930
+ field(:friends).type(:array).schema do
931
+ field(:name).type(:string)
932
+ end
933
+ end
934
+
935
+ result = schema.resolve(
936
+ friends: [
937
+ {name: 'Joe Bloggs'},
938
+ {name: 'Joan Bloggs'},
939
+ {name: 'Joe Bloggs'}
940
+ ]
941
+ )
942
+
943
+ result.valid? # => false
944
+ result.errors # => {'$' => ['friend names must be unique']}
945
+ ```
946
+
947
+ In most cases you should be validating individual fields using field policies. Only validate in before hooks in cases you have dependencies between fields.
948
+
949
+ `Schema#after_resolve` takes the sanitized input hash, and can be used to further validate fields that depend on eachother.
950
+
951
+ ```ruby
952
+ schema = Parametric::Schema.new do
953
+ after_resolve do |payload, ctx|
954
+ # Add a top level error using an arbitrary key name
955
+ ctx.add_base_error('deposit', 'cannot be greater than house price') if payload[:deposit] > payload[:house_price]
956
+ # Or add an error keyed after the current position in the schema
957
+ # ctx.add_error('some error') if some_condition
958
+ # after_resolve hooks must also return the payload, or a modified copy of it
959
+ # note that any changes added here won't be validated.
960
+ payload.merge(desc: 'hello')
961
+ end
962
+
963
+ field(:deposit).policy(:integer).present
964
+ field(:house_price).policy(:integer).present
965
+ field(:desc).policy(:string)
966
+ end
967
+
968
+ result = schema.resolve({ deposit: 1100, house_price: 1000 })
969
+ result.valid? # false
970
+ result.errors[:deposit] # ['cannot be greater than house price']
971
+ result.output[:deposit] # 1100
972
+ result.output[:house_price] # 1000
973
+ result.output[:desc] # 'hello'
974
+ ```
975
+
852
976
  ## Structs
853
977
 
854
978
  Structs turn schema definitions into objects graphs with attribute readers.
@@ -0,0 +1,53 @@
1
+ require 'benchmark/ips'
2
+ require 'parametric/struct'
3
+
4
+ StructAccount = Struct.new(:id, :email, keyword_init: true)
5
+ StructFriend = Struct.new(:name, keyword_init: true)
6
+ StructUser = Struct.new(:name, :age, :friends, :account, keyword_init: true)
7
+
8
+ class ParametricAccount
9
+ include Parametric::Struct
10
+ schema do
11
+ field(:id).type(:integer).present
12
+ field(:email).type(:string)
13
+ end
14
+ end
15
+
16
+ class ParametricUser
17
+ include Parametric::Struct
18
+ schema do
19
+ field(:name).type(:string).present
20
+ field(:age).type(:integer).default(42)
21
+ field(:friends).type(:array).schema do
22
+ field(:name).type(:string).present
23
+ end
24
+ field(:account).type(:object).schema ParametricAccount
25
+ end
26
+ end
27
+
28
+ Benchmark.ips do |x|
29
+ x.report("Struct") {
30
+ StructUser.new(
31
+ name: 'Ismael',
32
+ age: 42,
33
+ friends: [
34
+ StructFriend.new(name: 'Joe'),
35
+ StructFriend.new(name: 'Joan'),
36
+ ],
37
+ account: StructAccount.new(id: 123, email: 'my@account.com')
38
+ )
39
+ }
40
+ x.report("Parametric::Struct") {
41
+ ParametricUser.new!(
42
+ name: 'Ismael',
43
+ age: 42,
44
+ friends: [
45
+ { name: 'Joe' },
46
+ { name: 'Joan' }
47
+ ],
48
+ account: { id: 123, email: 'my@account.com' }
49
+ )
50
+ }
51
+ x.compare!
52
+ end
53
+
data/lib/parametric.rb CHANGED
@@ -1,3 +1,5 @@
1
+ # frozen_string_literal: true
2
+
1
3
  require "parametric/version"
2
4
  require "parametric/registry"
3
5
  require "parametric/field"
@@ -1,3 +1,5 @@
1
+ # frozen_string_literal: true
2
+
1
3
  module Parametric
2
4
  class BlockValidator
3
5
  def self.build(meth, &block)
@@ -8,12 +10,12 @@ module Parametric
8
10
 
9
11
  def self.message(&block)
10
12
  @message_block = block if block_given?
11
- @message_block
13
+ @message_block if instance_variable_defined?('@message_block')
12
14
  end
13
15
 
14
16
  def self.validate(&validate_block)
15
17
  @validate_block = validate_block if block_given?
16
- @validate_block
18
+ @validate_block if instance_variable_defined?('@validate_block')
17
19
  end
18
20
 
19
21
  def self.coerce(&coerce_block)
@@ -23,12 +25,12 @@ module Parametric
23
25
 
24
26
  def self.eligible(&block)
25
27
  @eligible_block = block if block_given?
26
- @eligible_block
28
+ @eligible_block if instance_variable_defined?('@eligible_block')
27
29
  end
28
30
 
29
31
  def self.meta_data(&block)
30
32
  @meta_data_block = block if block_given?
31
- @meta_data_block
33
+ @meta_data_block if instance_variable_defined?('@meta_data_block')
32
34
  end
33
35
 
34
36
  attr_reader :message
@@ -1,3 +1,5 @@
1
+ # frozen_string_literal: true
2
+
1
3
  module Parametric
2
4
  class Top
3
5
  attr_reader :errors
@@ -26,6 +28,10 @@ module Parametric
26
28
  top.add_error(string_path, msg)
27
29
  end
28
30
 
31
+ def add_base_error(key, msg)
32
+ top.add_error(key, msg)
33
+ end
34
+
29
35
  def sub(key)
30
36
  self.class.new(path + [key], top)
31
37
  end
@@ -40,5 +46,4 @@ module Parametric
40
46
  end.join
41
47
  end
42
48
  end
43
-
44
49
  end
@@ -1,3 +1,5 @@
1
+ # frozen_string_literal: true
2
+
1
3
  require "date"
2
4
 
3
5
  module Parametric
@@ -1,3 +1,5 @@
1
+ # frozen_string_literal: true
2
+
1
3
  require "parametric"
2
4
 
3
5
  module Parametric
@@ -1,3 +1,5 @@
1
+ # frozen_string_literal: true
2
+
1
3
  require "parametric/field_dsl"
2
4
 
3
5
  module Parametric
@@ -43,6 +45,15 @@ module Parametric
43
45
  policy sc.schema
44
46
  end
45
47
 
48
+ def from(another_field)
49
+ meta another_field.meta_data
50
+ another_field.policies.each do |plc|
51
+ policies << plc
52
+ end
53
+
54
+ self
55
+ end
56
+
46
57
  def visit(meta_key = nil, &visitor)
47
58
  if sc = meta_data[:schema]
48
59
  r = sc.visit(meta_key, &visitor)
@@ -83,8 +94,13 @@ module Parametric
83
94
  Result.new(eligible, value)
84
95
  end
85
96
 
97
+ protected
98
+
99
+ attr_reader :policies
100
+
86
101
  private
87
- attr_reader :policies, :registry, :default_block
102
+
103
+ attr_reader :registry, :default_block
88
104
 
89
105
  def resolve_one(policy, value, context)
90
106
  begin
@@ -1,3 +1,5 @@
1
+ # frozen_string_literal: true
2
+
1
3
  module Parametric
2
4
  # Field DSL
3
5
  # host instance must implement:
@@ -1,3 +1,5 @@
1
+ # frozen_string_literal: true
2
+
1
3
  module Parametric
2
4
  module Policies
3
5
  class Format
@@ -1,3 +1,5 @@
1
+ # frozen_string_literal: true
2
+
1
3
  require 'parametric/block_validator'
2
4
 
3
5
  module Parametric
@@ -1,3 +1,5 @@
1
+ # frozen_string_literal: true
2
+
1
3
  module Parametric
2
4
  class Results
3
5
  attr_reader :output, :errors
@@ -1,3 +1,5 @@
1
+ # frozen_string_literal: true
2
+
1
3
  require "parametric/context"
2
4
  require "parametric/results"
3
5
  require "parametric/field"
@@ -12,6 +14,8 @@ module Parametric
12
14
  @default_field_policies = []
13
15
  @ignored_field_keys = []
14
16
  @expansions = {}
17
+ @before_hooks = []
18
+ @after_hooks = []
15
19
  end
16
20
 
17
21
  def schema
@@ -90,6 +94,20 @@ module Parametric
90
94
  end
91
95
  end
92
96
 
97
+ def before_resolve(klass = nil, &block)
98
+ raise ArgumentError, '#before_resolve expects a callable object, or a block' if !klass && !block_given?
99
+ callable = klass || block
100
+ before_hooks << callable
101
+ self
102
+ end
103
+
104
+ def after_resolve(klass = nil, &block)
105
+ raise ArgumentError, '#after_resolve expects a callable object, or a block' if !klass && !block_given?
106
+ callable = klass || block
107
+ after_hooks << callable
108
+ self
109
+ end
110
+
93
111
  def expand(exp, &block)
94
112
  expansions[exp] = block
95
113
  self
@@ -139,19 +157,35 @@ module Parametric
139
157
 
140
158
  protected
141
159
 
142
- attr_reader :definitions, :options
160
+ attr_reader :definitions, :options, :before_hooks, :after_hooks
143
161
 
144
162
  private
145
163
 
146
164
  attr_reader :default_field_policies, :ignored_field_keys, :expansions
147
165
 
148
166
  def coerce_one(val, context, flds: fields)
149
- flds.each_with_object({}) do |(_, field), m|
167
+ val = run_before_hooks(val, context)
168
+
169
+ out = flds.each_with_object({}) do |(_, field), m|
150
170
  r = field.resolve(val, context.sub(field.key))
151
171
  if r.eligible?
152
172
  m[field.key] = r.value
153
173
  end
154
174
  end
175
+
176
+ run_after_hooks(out, context)
177
+ end
178
+
179
+ def run_before_hooks(val, context)
180
+ before_hooks.reduce(val) do |value, callable|
181
+ callable.call(value, context)
182
+ end
183
+ end
184
+
185
+ def run_after_hooks(val, context)
186
+ after_hooks.reduce(val) do |value, callable|
187
+ callable.call(value, context)
188
+ end
155
189
  end
156
190
 
157
191
  class MatchContext
@@ -1,3 +1,5 @@
1
+ # frozen_string_literal: true
2
+
1
3
  require 'parametric/dsl'
2
4
 
3
5
  module Parametric
@@ -36,6 +38,10 @@ module Parametric
36
38
  _results.output.clone
37
39
  end
38
40
 
41
+ def [](key)
42
+ _results.output[key.to_sym]
43
+ end
44
+
39
45
  def ==(other)
40
46
  other.respond_to?(:to_h) && other.to_h.eql?(to_h)
41
47
  end
@@ -56,10 +62,24 @@ module Parametric
56
62
 
57
63
  # this hook is called after schema definition in DSL module
58
64
  def parametric_after_define_schema(schema)
59
- schema.fields.keys.each do |key|
60
- define_method key do
61
- _graph[key]
65
+ schema.fields.values.each do |field|
66
+ if field.meta_data[:schema]
67
+ if field.meta_data[:schema].is_a?(Parametric::Schema)
68
+ klass = Class.new do
69
+ include Struct
70
+ end
71
+ klass.schema = field.meta_data[:schema]
72
+ self.const_set(__class_name(field.key), klass)
73
+ klass.parametric_after_define_schema(field.meta_data[:schema])
74
+ else
75
+ self.const_set(__class_name(field.key), field.meta_data[:schema])
76
+ end
62
77
  end
78
+ self.class_eval <<-RUBY, __FILE__, __LINE__ + 1
79
+ def #{field.key}
80
+ _graph[:#{field.key}]
81
+ end
82
+ RUBY
63
83
  end
64
84
  end
65
85
 
@@ -69,27 +89,11 @@ module Parametric
69
89
  end
70
90
  end
71
91
 
72
- def parametric_build_class_for_child(key, child_schema)
73
- klass = Class.new do
74
- include Struct
75
- end
76
- klass.schema = child_schema
77
- klass
78
- end
79
-
80
92
  def wrap(key, value)
81
- field = schema.fields[key]
82
- return value unless field
83
-
84
93
  case value
85
94
  when Hash
86
95
  # find constructor for field
87
- cons = field.meta_data[:schema]
88
- if cons.kind_of?(Parametric::Schema)
89
- klass = parametric_build_class_for_child(key, cons)
90
- klass.parametric_after_define_schema(cons)
91
- cons = klass
92
- end
96
+ cons = self.const_get(__class_name(key))
93
97
  cons ? cons.new(value) : value.freeze
94
98
  when Array
95
99
  value.map{|v| wrap(key, v) }.freeze
@@ -97,6 +101,12 @@ module Parametric
97
101
  value.freeze
98
102
  end
99
103
  end
104
+
105
+ PLURAL_END = /s$/.freeze
106
+
107
+ def __class_name(key)
108
+ key.to_s.split('_').map(&:capitalize).join.sub(PLURAL_END, '')
109
+ end
100
110
  end
101
111
  end
102
112
  end
@@ -1,3 +1,5 @@
1
+ # frozen_string_literal: true
2
+
1
3
  module Parametric
2
- VERSION = "0.2.9"
4
+ VERSION = "0.2.14"
3
5
  end
data/parametric.gemspec CHANGED
@@ -17,7 +17,6 @@ Gem::Specification.new do |spec|
17
17
  spec.test_files = spec.files.grep(%r{^(test|spec|features)/})
18
18
  spec.require_paths = ["lib"]
19
19
 
20
- spec.add_development_dependency "bundler", "~> 1.5"
21
20
  spec.add_development_dependency "rake"
22
21
  spec.add_development_dependency "rspec", '3.4.0'
23
22
  spec.add_development_dependency "byebug"
data/spec/field_spec.rb CHANGED
@@ -112,6 +112,20 @@ describe Parametric::Field do
112
112
  end
113
113
  end
114
114
 
115
+ describe '#from' do
116
+ it 'copies policies and metadata from an existing field' do
117
+ subject.policy(:string).present.options(['a', 'b', 'c'])
118
+
119
+ field = described_class.new(:another_key, registry).from(subject)
120
+ resolve(field, another_key: "abc").tap do |r|
121
+ has_errors
122
+ end
123
+ expect(field.meta_data[:type]).to eq(:string)
124
+ expect(field.meta_data[:present]).to be(true)
125
+ expect(field.meta_data[:options]).to eq(['a', 'b', 'c'])
126
+ end
127
+ end
128
+
115
129
  describe "#present" do
116
130
  it "is valid if value is present" do
117
131
  resolve(subject.present, a_key: "abc").tap do |r|
@@ -2,7 +2,7 @@ require 'spec_helper'
2
2
 
3
3
  describe 'default coercions' do
4
4
  def test_coercion(key, value, expected)
5
- coercion = Parametric.registry.coercions[key]
5
+ coercion = Parametric.registry.policies[key]
6
6
  expect(coercion.new.coerce(value, nil, nil)).to eq expected
7
7
  end
8
8
 
@@ -0,0 +1,133 @@
1
+ require 'spec_helper'
2
+
3
+ describe Parametric::Schema do
4
+ describe '#before_resolve' do
5
+ it 'passes payload through before_resolve block, if defined' do
6
+ schema = described_class.new do
7
+ before_resolve do |payload, _context|
8
+ payload[:slug] = payload[:name].to_s.downcase.gsub(/\s+/, '-') unless payload[:slug]
9
+ payload
10
+ end
11
+
12
+ field(:name).policy(:string).present
13
+ field(:slug).policy(:string).present
14
+ field(:variants).policy(:array).schema do
15
+ before_resolve do |payload, _context|
16
+ payload[:slug] = "v: #{payload[:name].to_s.downcase}"
17
+ payload
18
+ end
19
+ field(:name).policy(:string).present
20
+ field(:slug).type(:string).present
21
+ end
22
+ end
23
+
24
+ result = schema.resolve({ name: 'A name', variants: [{ name: 'A variant' }] })
25
+ expect(result.valid?).to be true
26
+ expect(result.output[:slug]).to eq 'a-name'
27
+ expect(result.output[:variants].first[:slug]).to eq 'v: a variant'
28
+ end
29
+
30
+ it 'collects errors added in before_resolve blocks' do
31
+ schema = described_class.new do
32
+ field(:variants).type(:array).schema do
33
+ before_resolve do |payload, context|
34
+ context.add_error 'nope!' if payload[:name] == 'with errors'
35
+ payload
36
+ end
37
+ field(:name).type(:string)
38
+ end
39
+ end
40
+
41
+ results = schema.resolve({ variants: [ {name: 'no errors'}, {name: 'with errors'}]})
42
+ expect(results.valid?).to be false
43
+ expect(results.errors['$.variants[1]']).to eq ['nope!']
44
+ end
45
+
46
+ it 'copies before_resolve hooks to merged schemas' do
47
+ schema1 = described_class.new do
48
+ before_resolve do |payload, _context|
49
+ payload[:slug] = payload[:name].to_s.downcase.gsub(/\s+/, '-') unless payload[:slug]
50
+ payload
51
+ end
52
+ field(:name).present.type(:string)
53
+ field(:slug).present.type(:string)
54
+ end
55
+
56
+ schema2 = described_class.new do
57
+ before_resolve do |payload, _context|
58
+ payload[:slug] = "slug-#{payload[:slug]}" if payload[:slug]
59
+ payload
60
+ end
61
+
62
+ field(:age).type(:integer)
63
+ end
64
+
65
+ schema3 = schema1.merge(schema2)
66
+
67
+ results = schema3.resolve({ name: 'Ismael Celis', age: 41 })
68
+ expect(results.output[:slug]).to eq 'slug-ismael-celis'
69
+ end
70
+
71
+ it 'works with any callable' do
72
+ slug_maker = Class.new do
73
+ def initialize(slug_field, from:)
74
+ @slug_field, @from = slug_field, from
75
+ end
76
+
77
+ def call(payload, _context)
78
+ payload.merge(
79
+ @slug_field => payload[@from].to_s.downcase.gsub(/\s+/, '-')
80
+ )
81
+ end
82
+ end
83
+
84
+ schema = described_class.new do |sc, _opts|
85
+ sc.before_resolve slug_maker.new(:slug, from: :name)
86
+
87
+ sc.field(:name).type(:string)
88
+ sc.field(:slug).type(:string)
89
+ end
90
+
91
+ results = schema.resolve(name: 'Ismael Celis')
92
+ expect(results.output[:slug]).to eq 'ismael-celis'
93
+ end
94
+ end
95
+
96
+ describe '#after_resolve' do
97
+ let!(:schema) do
98
+ described_class.new do
99
+ after_resolve do |payload, ctx|
100
+ ctx.add_base_error('deposit', 'cannot be greater than house price') if payload[:deposit] > payload[:house_price]
101
+ payload.merge(desc: 'hello')
102
+ end
103
+
104
+ field(:deposit).policy(:integer).present
105
+ field(:house_price).policy(:integer).present
106
+ field(:desc).policy(:string)
107
+ end
108
+ end
109
+
110
+ it 'passes payload through after_resolve block, if defined' do
111
+ result = schema.resolve({ deposit: 1100, house_price: 1000 })
112
+ expect(result.valid?).to be false
113
+ expect(result.output[:deposit]).to eq 1100
114
+ expect(result.output[:house_price]).to eq 1000
115
+ expect(result.output[:desc]).to eq 'hello'
116
+ end
117
+
118
+ it 'copies after hooks when merging schemas' do
119
+ child_schema = described_class.new do
120
+ field(:name).type(:string)
121
+ end
122
+
123
+ union = schema.merge(child_schema)
124
+
125
+ result = union.resolve({ name: 'Joe', deposit: 1100, house_price: 1000 })
126
+ expect(result.valid?).to be false
127
+ expect(result.output[:deposit]).to eq 1100
128
+ expect(result.output[:house_price]).to eq 1000
129
+ expect(result.output[:desc]).to eq 'hello'
130
+ expect(result.output[:name]).to eq 'Joe'
131
+ end
132
+ end
133
+ end
data/spec/struct_spec.rb CHANGED
@@ -40,6 +40,10 @@ describe Parametric::Struct do
40
40
  expect(instance.friends.first.name).to eq 'Ismael'
41
41
  expect(instance.friends.first).to be_a friend_class
42
42
 
43
+ # Hash access with #[]
44
+ expect(instance[:title]).to eq instance.title
45
+ expect(instance[:friends].first[:name]).to eq instance.friends.first.name
46
+
43
47
  invalid_instance = klass.new({
44
48
  friends: [
45
49
  {name: 'Ismael', age: 40},
@@ -99,32 +103,6 @@ describe Parametric::Struct do
99
103
  expect(instance.friends.first.age).to eq 10
100
104
  end
101
105
 
102
- it 'wraps nested schemas in custom class' do
103
- klass = Class.new do
104
- include Parametric::Struct
105
-
106
- def self.parametric_build_class_for_child(key, child_schema)
107
- Class.new do
108
- include Parametric::Struct
109
- schema child_schema
110
- def salutation
111
- "my age is #{age}"
112
- end
113
- end
114
- end
115
-
116
- schema do
117
- field(:name).type(:string).present
118
- field(:friends).type(:array).schema do
119
- field(:age).type(:integer)
120
- end
121
- end
122
- end
123
-
124
- user = klass.new(name: 'Ismael', friends: [{age: 43}])
125
- expect(user.friends.first.salutation).to eq 'my age is 43'
126
- end
127
-
128
106
  it "wraps regular schemas in structs" do
129
107
  friend_schema = Parametric::Schema.new do
130
108
  field(:name)
metadata CHANGED
@@ -1,29 +1,15 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: parametric
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.2.9
4
+ version: 0.2.14
5
5
  platform: ruby
6
6
  authors:
7
7
  - Ismael Celis
8
- autorequire:
8
+ autorequire:
9
9
  bindir: bin
10
10
  cert_chain: []
11
- date: 2019-08-07 00:00:00.000000000 Z
11
+ date: 2021-03-11 00:00:00.000000000 Z
12
12
  dependencies:
13
- - !ruby/object:Gem::Dependency
14
- name: bundler
15
- requirement: !ruby/object:Gem::Requirement
16
- requirements:
17
- - - "~>"
18
- - !ruby/object:Gem::Version
19
- version: '1.5'
20
- type: :development
21
- prerelease: false
22
- version_requirements: !ruby/object:Gem::Requirement
23
- requirements:
24
- - - "~>"
25
- - !ruby/object:Gem::Version
26
- version: '1.5'
27
13
  - !ruby/object:Gem::Dependency
28
14
  name: rake
29
15
  requirement: !ruby/object:Gem::Requirement
@@ -81,6 +67,7 @@ files:
81
67
  - LICENSE.txt
82
68
  - README.md
83
69
  - Rakefile
70
+ - bench/struct_bench.rb
84
71
  - bin/console
85
72
  - lib/parametric.rb
86
73
  - lib/parametric/block_validator.rb
@@ -101,6 +88,7 @@ files:
101
88
  - spec/expand_spec.rb
102
89
  - spec/field_spec.rb
103
90
  - spec/policies_spec.rb
91
+ - spec/schema_lifecycle_hooks_spec.rb
104
92
  - spec/schema_spec.rb
105
93
  - spec/schema_walk_spec.rb
106
94
  - spec/spec_helper.rb
@@ -110,7 +98,7 @@ homepage: ''
110
98
  licenses:
111
99
  - MIT
112
100
  metadata: {}
113
- post_install_message:
101
+ post_install_message:
114
102
  rdoc_options: []
115
103
  require_paths:
116
104
  - lib
@@ -125,9 +113,8 @@ required_rubygems_version: !ruby/object:Gem::Requirement
125
113
  - !ruby/object:Gem::Version
126
114
  version: '0'
127
115
  requirements: []
128
- rubyforge_project:
129
- rubygems_version: 2.7.7
130
- signing_key:
116
+ rubygems_version: 3.0.3
117
+ signing_key:
131
118
  specification_version: 4
132
119
  summary: DSL for declaring allowed parameters with options, regexp patern and default
133
120
  values.
@@ -137,6 +124,7 @@ test_files:
137
124
  - spec/expand_spec.rb
138
125
  - spec/field_spec.rb
139
126
  - spec/policies_spec.rb
127
+ - spec/schema_lifecycle_hooks_spec.rb
140
128
  - spec/schema_spec.rb
141
129
  - spec/schema_walk_spec.rb
142
130
  - spec/spec_helper.rb