parametric 0.0.5 → 0.1.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.
@@ -0,0 +1,151 @@
1
+ require "parametric/context"
2
+ require "parametric/results"
3
+ require "parametric/field"
4
+
5
+ module Parametric
6
+ class Schema
7
+ def initialize(options = {}, &block)
8
+ @options = options
9
+ @fields = {}
10
+ @definitions = []
11
+ @definitions << block if block_given?
12
+ @default_field_policies = []
13
+ @ignored_field_keys = []
14
+ end
15
+
16
+ def fields
17
+ apply!
18
+ @fields
19
+ end
20
+
21
+ def policy(*names, &block)
22
+ @default_field_policies = names
23
+ definitions << block if block_given?
24
+
25
+ self
26
+ end
27
+
28
+ def ignore(*field_keys, &block)
29
+ @ignored_field_keys += field_keys
30
+ @ignored_field_keys.uniq!
31
+
32
+ definitions << block if block_given?
33
+
34
+ self
35
+ end
36
+
37
+ def clone
38
+ instance = self.class.new(options)
39
+ copy_into instance
40
+ end
41
+
42
+ def merge(other_schema)
43
+ instance = self.class.new(options.merge(other_schema.options))
44
+
45
+ copy_into(instance)
46
+ other_schema.copy_into(instance)
47
+ end
48
+
49
+ def copy_into(instance)
50
+ instance.policy(*default_field_policies) if default_field_policies.any?
51
+
52
+ definitions.each do |d|
53
+ instance.definitions << d
54
+ end
55
+
56
+ instance.ignore *ignored_field_keys
57
+ instance
58
+ end
59
+
60
+ def structure
61
+ fields.each_with_object({}) do |(_, field), obj|
62
+ meta = field.meta_data.dup
63
+ sc = meta.delete(:schema)
64
+ meta[:structure] = sc.structure if sc
65
+ obj[field.key] = meta
66
+ end
67
+ end
68
+
69
+ def field(field_or_key)
70
+ f, key = if field_or_key.kind_of?(Field)
71
+ [field_or_key, field_or_key.key]
72
+ else
73
+ [Field.new(field_or_key), field_or_key.to_sym]
74
+ end
75
+
76
+ if ignored_field_keys.include?(f.key)
77
+ f
78
+ else
79
+ @fields[key] = apply_default_field_policies_to(f)
80
+ end
81
+ end
82
+
83
+ def resolve(payload)
84
+ context = Context.new
85
+ output = coerce(payload, nil, context)
86
+ Results.new(output, context.errors)
87
+ end
88
+
89
+ def walk(meta_key = nil, &visitor)
90
+ r = visit(meta_key, &visitor)
91
+ Results.new(r, {})
92
+ end
93
+
94
+ def eligible?(value, key, payload)
95
+ payload.key? key
96
+ end
97
+
98
+ def valid?(*_)
99
+ true
100
+ end
101
+
102
+ def meta_data
103
+ {}
104
+ end
105
+
106
+ def visit(meta_key = nil, &visitor)
107
+ fields.each_with_object({}) do |(_, field), m|
108
+ m[field.key] = field.visit(meta_key, &visitor)
109
+ end
110
+ end
111
+
112
+ def coerce(val, _, context)
113
+ if val.is_a?(Array)
114
+ val.map.with_index{|v, idx|
115
+ coerce_one(v, context.sub(idx))
116
+ }
117
+ else
118
+ coerce_one val, context
119
+ end
120
+ end
121
+
122
+ protected
123
+
124
+ attr_reader :definitions, :options
125
+
126
+ private
127
+
128
+ attr_reader :default_field_policies, :ignored_field_keys
129
+
130
+ def coerce_one(val, context)
131
+ fields.each_with_object({}) do |(_, field), m|
132
+ r = field.resolve(val, context.sub(field.key))
133
+ if r.eligible?
134
+ m[field.key] = r.value
135
+ end
136
+ end
137
+ end
138
+
139
+ def apply_default_field_policies_to(field)
140
+ default_field_policies.reduce(field) {|f, policy_name| f.policy(policy_name) }
141
+ end
142
+
143
+ def apply!
144
+ return if @applied
145
+ definitions.each do |d|
146
+ self.instance_exec(options, &d)
147
+ end
148
+ @applied = true
149
+ end
150
+ end
151
+ end
@@ -1,3 +1,3 @@
1
1
  module Parametric
2
- VERSION = "0.0.5"
2
+ VERSION = "0.1.0"
3
3
  end
data/lib/parametric.rb CHANGED
@@ -1,10 +1,20 @@
1
+ require "parametric/version"
2
+ require "parametric/registry"
3
+ require "parametric/field"
4
+ require "parametric/results"
5
+ require "parametric/schema"
6
+ require "parametric/context"
7
+
1
8
  module Parametric
2
9
 
10
+ def self.registry
11
+ @registry ||= Registry.new
12
+ end
13
+
14
+ def self.policy(name, plcy = nil, &block)
15
+ registry.policy name, plcy, &block
16
+ end
3
17
  end
4
18
 
5
- require "parametric/utils"
6
- require "parametric/version"
7
- require "parametric/policies"
8
- require "parametric/params"
9
- require "parametric/typed_params"
10
- require "parametric/hash"
19
+ require 'parametric/default_types'
20
+ require 'parametric/policies'
data/parametric.gemspec CHANGED
@@ -20,5 +20,6 @@ Gem::Specification.new do |spec|
20
20
 
21
21
  spec.add_development_dependency "bundler", "~> 1.5"
22
22
  spec.add_development_dependency "rake"
23
- spec.add_development_dependency "rspec", '2.14.1'
23
+ spec.add_development_dependency "rspec", '3.4.0'
24
+ spec.add_development_dependency "byebug"
24
25
  end
data/spec/dsl_spec.rb ADDED
@@ -0,0 +1,135 @@
1
+ require 'spec_helper'
2
+ require "parametric/dsl"
3
+
4
+ describe "classes including DSL module" do
5
+ class Parent
6
+ include Parametric::DSL
7
+
8
+ schema(age_type: :integer) do |opts|
9
+ field(:title).policy(:string)
10
+ field(:age).policy(opts[:age_type])
11
+ end
12
+ end
13
+
14
+ class Child < Parent
15
+ schema(age_type: :string) do
16
+ field(:description).policy(:string)
17
+ end
18
+ end
19
+
20
+ class GrandChild < Child
21
+ schema(age_type: :integer)
22
+ end
23
+
24
+ describe "#schema" do
25
+ let(:input) {
26
+ {
27
+ title: "A title",
28
+ age: 38,
29
+ description: "A description"
30
+ }
31
+ }
32
+
33
+ it "merges parent's schema into child's" do
34
+ parent_output = Parent.schema.resolve(input).output
35
+ child_output = Child.schema.resolve(input).output
36
+
37
+ expect(parent_output.keys).to match_array([:title, :age])
38
+ expect(parent_output[:title]).to eq "A title"
39
+ expect(parent_output[:age]).to eq 38
40
+
41
+ expect(child_output.keys).to match_array([:title, :age, :description])
42
+ expect(child_output[:title]).to eq "A title"
43
+ expect(child_output[:age]).to eq "38"
44
+ expect(child_output[:description]).to eq "A description"
45
+ end
46
+
47
+ it "inherits options" do
48
+ grand_child_output = GrandChild.schema.resolve(input).output
49
+
50
+ expect(grand_child_output.keys).to match_array([:title, :age, :description])
51
+ expect(grand_child_output[:title]).to eq "A title"
52
+ expect(grand_child_output[:age]).to eq 38
53
+ expect(grand_child_output[:description]).to eq "A description"
54
+ end
55
+ end
56
+
57
+ describe "inheriting schema policy" do
58
+ let!(:a) {
59
+ Class.new do
60
+ include Parametric::DSL
61
+
62
+ schema.policy(:present) do
63
+ field(:title).policy(:string)
64
+ end
65
+ end
66
+ }
67
+
68
+ let!(:b) {
69
+ Class.new(a)
70
+ }
71
+
72
+ it "inherits policy" do
73
+ results = a.schema.resolve({})
74
+ expect(results.errors["$.title"]).not_to be_empty
75
+
76
+ results = b.schema.resolve({})
77
+ expect(results.errors["$.title"]).not_to be_empty
78
+ end
79
+ end
80
+
81
+ describe "overriding schema policy" do
82
+ let!(:a) {
83
+ Class.new do
84
+ include Parametric::DSL
85
+
86
+ schema.policy(:present) do
87
+ field(:title).policy(:string)
88
+ end
89
+ end
90
+ }
91
+
92
+ let!(:b) {
93
+ Class.new(a) do
94
+ schema.policy(:declared)
95
+ end
96
+ }
97
+
98
+ it "does not mutate parent schema" do
99
+ results = a.schema.resolve({})
100
+ expect(results.errors).not_to be_empty
101
+
102
+ results = b.schema.resolve({})
103
+ expect(results.errors).to be_empty
104
+ end
105
+ end
106
+
107
+ describe "removes fields defined in the parent class" do
108
+ let!(:a) {
109
+ Class.new do
110
+ include Parametric::DSL
111
+
112
+ schema do
113
+ field(:title).policy(:string)
114
+ end
115
+ end
116
+ }
117
+
118
+ let!(:b) {
119
+ Class.new(a) do
120
+ schema.ignore(:title) do
121
+ field(:age)
122
+ end
123
+ end
124
+ }
125
+
126
+ it "removes inherited field from child class" do
127
+ results = a.schema.resolve({title: "Mr.", age: 20})
128
+ expect(results.output).to eq({title: "Mr."})
129
+
130
+ results = b.schema.resolve({title: "Mr.", age: 20})
131
+ expect(results.output).to eq({age: 20})
132
+ end
133
+ end
134
+ end
135
+