parametric 0.0.5 → 0.1.0

Sign up to get free protection for your applications and to get access to all the features.
@@ -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
+