trailblazer 1.1.2 → 2.0.0.beta1
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/.travis.yml +10 -7
- data/CHANGES.md +108 -0
- data/COMM-LICENSE +91 -0
- data/Gemfile +18 -4
- data/LICENSE.txt +7 -20
- data/README.md +55 -15
- data/Rakefile +21 -2
- data/draft-1.2.rb +7 -0
- data/lib/trailblazer.rb +17 -4
- data/lib/trailblazer/dsl.rb +47 -0
- data/lib/trailblazer/operation/auto_inject.rb +47 -0
- data/lib/trailblazer/operation/builder.rb +18 -18
- data/lib/trailblazer/operation/callback.rb +31 -38
- data/lib/trailblazer/operation/contract.rb +46 -0
- data/lib/trailblazer/operation/controller.rb +45 -27
- data/lib/trailblazer/operation/guard.rb +24 -0
- data/lib/trailblazer/operation/model.rb +41 -33
- data/lib/trailblazer/operation/nested.rb +43 -0
- data/lib/trailblazer/operation/params.rb +13 -0
- data/lib/trailblazer/operation/persist.rb +13 -0
- data/lib/trailblazer/operation/policy.rb +26 -72
- data/lib/trailblazer/operation/present.rb +19 -0
- data/lib/trailblazer/operation/procedural/contract.rb +15 -0
- data/lib/trailblazer/operation/procedural/validate.rb +22 -0
- data/lib/trailblazer/operation/pundit.rb +42 -0
- data/lib/trailblazer/operation/representer.rb +25 -92
- data/lib/trailblazer/operation/rescue.rb +23 -0
- data/lib/trailblazer/operation/resolver.rb +18 -24
- data/lib/trailblazer/operation/validate.rb +50 -0
- data/lib/trailblazer/operation/wrap.rb +37 -0
- data/lib/trailblazer/version.rb +1 -1
- data/test/{operation/controller_test.rb → controller_test.rb} +8 -4
- data/test/docs/auto_inject_test.rb +30 -0
- data/test/docs/contract_test.rb +429 -0
- data/test/docs/dry_test.rb +31 -0
- data/test/docs/guard_test.rb +143 -0
- data/test/docs/nested_test.rb +117 -0
- data/test/docs/policy_test.rb +2 -0
- data/test/docs/pundit_test.rb +109 -0
- data/test/docs/representer_test.rb +268 -0
- data/test/docs/rescue_test.rb +153 -0
- data/test/docs/wrap_test.rb +174 -0
- data/test/gemfiles/Gemfile.ruby-1.9 +3 -0
- data/test/gemfiles/Gemfile.ruby-2.0 +12 -0
- data/test/gemfiles/Gemfile.ruby-2.3 +12 -0
- data/test/module_test.rb +22 -15
- data/test/operation/builder_test.rb +66 -18
- data/test/operation/callback_test.rb +70 -0
- data/test/operation/contract_test.rb +385 -15
- data/test/operation/dsl/callback_test.rb +18 -30
- data/test/operation/dsl/contract_test.rb +209 -19
- data/test/operation/dsl/representer_test.rb +42 -15
- data/test/operation/guard_test.rb +1 -147
- data/test/operation/model_test.rb +105 -0
- data/test/operation/params_test.rb +36 -0
- data/test/operation/persist_test.rb +44 -0
- data/test/operation/pipedream_test.rb +59 -0
- data/test/operation/pipetree_test.rb +104 -0
- data/test/operation/present_test.rb +24 -0
- data/test/operation/pundit_test.rb +104 -0
- data/test/{representer_test.rb → operation/representer_test.rb} +58 -42
- data/test/operation/resolver_test.rb +34 -70
- data/test/operation_test.rb +57 -189
- data/test/test_helper.rb +23 -3
- data/trailblazer.gemspec +8 -7
- metadata +91 -59
- data/gemfiles/Gemfile.rails.lock +0 -130
- data/gemfiles/Gemfile.reform-2.0 +0 -6
- data/gemfiles/Gemfile.reform-2.1 +0 -7
- data/lib/trailblazer/autoloading.rb +0 -15
- data/lib/trailblazer/endpoint.rb +0 -31
- data/lib/trailblazer/operation.rb +0 -175
- data/lib/trailblazer/operation/collection.rb +0 -6
- data/lib/trailblazer/operation/dispatch.rb +0 -3
- data/lib/trailblazer/operation/model/dsl.rb +0 -29
- data/lib/trailblazer/operation/model/external.rb +0 -34
- data/lib/trailblazer/operation/policy/guard.rb +0 -35
- data/lib/trailblazer/operation/uploaded_file.rb +0 -77
- data/test/callback_test.rb +0 -104
- data/test/collection_test.rb +0 -57
- data/test/model_test.rb +0 -148
- data/test/operation/external_model_test.rb +0 -71
- data/test/operation/policy_test.rb +0 -97
- data/test/operation/reject_test.rb +0 -34
- data/test/rollback_test.rb +0 -47
@@ -0,0 +1,24 @@
|
|
1
|
+
require "trailblazer/operation/policy"
|
2
|
+
require "uber/option"
|
3
|
+
|
4
|
+
class Trailblazer::Operation
|
5
|
+
module Policy
|
6
|
+
module Guard
|
7
|
+
def self.import!(operation, import, user_proc, options={})
|
8
|
+
Policy.add!(operation, import, options) { Guard.build(user_proc) }
|
9
|
+
end
|
10
|
+
|
11
|
+
def self.build(callable)
|
12
|
+
value = Uber::Option[callable]
|
13
|
+
|
14
|
+
# call'ing the Uber::Option will run either proc or block.
|
15
|
+
# this gets wrapped in a Operation::Result object.
|
16
|
+
->(options) { Result.new( !!value.(options), {} ) }
|
17
|
+
end
|
18
|
+
end # Guard
|
19
|
+
|
20
|
+
def self.Guard(*args, &block)
|
21
|
+
[ Guard, args, block ]
|
22
|
+
end
|
23
|
+
end
|
24
|
+
end
|
@@ -1,50 +1,58 @@
|
|
1
|
-
|
2
|
-
|
3
|
-
|
4
|
-
|
5
|
-
|
6
|
-
|
7
|
-
|
8
|
-
|
9
|
-
base.extend DSL
|
1
|
+
class Trailblazer::Operation
|
2
|
+
module Model
|
3
|
+
Step = ->(operation, options) { options["model"] = operation.model!(options["params"]) }
|
4
|
+
|
5
|
+
def self.import!(operation, import, model_class, action=nil)
|
6
|
+
if import.inheriting? # not sure how to do overrides!
|
7
|
+
# FIXME: prototyping inheritance. should we handle that here?
|
8
|
+
return operation["model.action"] = model_class
|
10
9
|
end
|
11
10
|
|
12
|
-
|
13
|
-
module BuildModel
|
14
|
-
def model!(params)
|
15
|
-
instantiate_model(params)
|
16
|
-
end
|
11
|
+
import.(:&, Step, name: "model.build")
|
17
12
|
|
18
|
-
|
19
|
-
|
20
|
-
end
|
13
|
+
operation["model.class"] = model_class
|
14
|
+
operation["model.action"] = action
|
21
15
|
|
22
|
-
|
23
|
-
|
24
|
-
end
|
16
|
+
operation.send :include, BuildMethods
|
17
|
+
end
|
25
18
|
|
26
|
-
|
27
|
-
|
28
|
-
|
19
|
+
# Methods to create the model according to class configuration and params.
|
20
|
+
module BuildMethods
|
21
|
+
def model_class
|
22
|
+
self["model.class"] or raise "[Trailblazer] You didn't call Operation::model."
|
23
|
+
end
|
29
24
|
|
30
|
-
|
25
|
+
def action_name
|
26
|
+
self["model.action"] or :new
|
31
27
|
end
|
32
28
|
|
29
|
+
def model!(params)
|
30
|
+
instantiate_model(params)
|
31
|
+
end
|
33
32
|
|
34
|
-
|
35
|
-
|
36
|
-
super(params, model, *args)
|
33
|
+
def instantiate_model(params)
|
34
|
+
send("#{action_name}_model", params)
|
37
35
|
end
|
38
36
|
|
39
|
-
|
40
|
-
|
37
|
+
def new_model(params)
|
38
|
+
model_class.new
|
39
|
+
end
|
41
40
|
|
42
|
-
def
|
43
|
-
|
41
|
+
def update_model(params)
|
42
|
+
model_class.find(params[:id])
|
44
43
|
end
|
45
|
-
|
46
|
-
|
44
|
+
|
45
|
+
alias_method :find_model, :update_model
|
46
|
+
|
47
|
+
# Doesn't throw an exception and will return false to divert to Left.
|
48
|
+
def find_by_model(params)
|
49
|
+
model = model_class.find_by(id: params[:id])
|
50
|
+
|
51
|
+
self["result.model"] = Result.new(!model.nil?, {})
|
52
|
+
model
|
47
53
|
end
|
48
54
|
end
|
49
55
|
end
|
56
|
+
|
57
|
+
DSL.macro!(:Model, Model)
|
50
58
|
end
|
@@ -0,0 +1,43 @@
|
|
1
|
+
class Trailblazer::Operation
|
2
|
+
module Nested
|
3
|
+
# Please note that the instance_variable_get are here on purpose since the
|
4
|
+
# superinternal API is not entirely decided, yet.
|
5
|
+
def self.import!(operation, import, step)
|
6
|
+
import.(:&, ->(input, options) {
|
7
|
+
result = step._call(*options.to_runtime_data)
|
8
|
+
|
9
|
+
result.instance_variable_get(:@data).to_mutable_data.each do |k,v|
|
10
|
+
options[k] = v
|
11
|
+
end
|
12
|
+
|
13
|
+
result.success? # DISCUSS: what if we could simply return the result object here?
|
14
|
+
}, {} )
|
15
|
+
end
|
16
|
+
end
|
17
|
+
|
18
|
+
DSL.macro!(:Nested, Nested)
|
19
|
+
|
20
|
+
module Rescue
|
21
|
+
def self.import!(_operation, import, *exceptions, handler:->(*){}, &block)
|
22
|
+
exceptions = [StandardError] unless exceptions.any?
|
23
|
+
handler = Pipetree::DSL::Option.(handler)
|
24
|
+
|
25
|
+
rescue_block = ->(options, operation, *, &nested_pipe) {
|
26
|
+
begin
|
27
|
+
res = nested_pipe.call
|
28
|
+
res.first == ::Pipetree::Flow::Right # FIXME.
|
29
|
+
rescue *exceptions => exception
|
30
|
+
handler.call(operation, exception, options)
|
31
|
+
#options["result.model.find"] = "argh! because #{exception.class}"
|
32
|
+
false
|
33
|
+
end
|
34
|
+
}
|
35
|
+
|
36
|
+
# operation.| operation.Wrap(rescue_block, &block), name: "Rescue:#{block.source_location.last}"
|
37
|
+
Wrap.import! _operation, import, rescue_block, name: "Rescue:#{block.source_location.last}", &block
|
38
|
+
end
|
39
|
+
end
|
40
|
+
|
41
|
+
DSL.macro!(:Rescue, Rescue)
|
42
|
+
end
|
43
|
+
|
@@ -0,0 +1,13 @@
|
|
1
|
+
class Trailblazer::Operation
|
2
|
+
module Params
|
3
|
+
def self.included(includer)
|
4
|
+
includer.> Replace, after: New
|
5
|
+
end
|
6
|
+
end
|
7
|
+
|
8
|
+
# Returned object will replace "params". Original is saved in "params.original".
|
9
|
+
Params::Replace = ->(input, options) {
|
10
|
+
options["params.original"] = original = options["params"]
|
11
|
+
options["params"] = input.params!(original)
|
12
|
+
}
|
13
|
+
end
|
@@ -0,0 +1,13 @@
|
|
1
|
+
class Trailblazer::Operation
|
2
|
+
module Persist
|
3
|
+
def self.import!(operation, import, options={})
|
4
|
+
save_method = options[:method] || :save
|
5
|
+
contract_name = options[:name] || "contract.default"
|
6
|
+
|
7
|
+
import.(:&, ->(input, options) { options[contract_name].send(save_method) }, # TODO: test me.
|
8
|
+
name: "persist.save")
|
9
|
+
end
|
10
|
+
end
|
11
|
+
|
12
|
+
DSL.macro!(:Persist, Persist)
|
13
|
+
end
|
@@ -1,85 +1,39 @@
|
|
1
|
-
|
1
|
+
class Trailblazer::Operation
|
2
|
+
module Policy
|
3
|
+
# Step: This generically `call`s a policy and then pushes its result to `options`.
|
4
|
+
# You can use any callable object as a policy with this step.
|
5
|
+
# :private:
|
6
|
+
class Eval
|
7
|
+
include Uber::Callable
|
2
8
|
|
3
|
-
|
4
|
-
|
5
|
-
|
6
|
-
|
7
|
-
# Adds #evaluate_policy to #setup!, and ::policy.
|
8
|
-
module Operation::Policy
|
9
|
-
def self.included(includer)
|
10
|
-
includer.extend DSL
|
11
|
-
end
|
12
|
-
|
13
|
-
module DSL
|
14
|
-
def self.extended(extender)
|
15
|
-
extender.inheritable_attr :policy_config
|
16
|
-
extender.policy_config = lambda { |*| true } # return true per default.
|
9
|
+
def initialize(name:nil, path:nil)
|
10
|
+
@name = name
|
11
|
+
@path = path
|
17
12
|
end
|
18
13
|
|
19
|
-
def
|
20
|
-
|
21
|
-
|
14
|
+
def call(input, options)
|
15
|
+
condition = options[@path] # this allows dependency injection.
|
16
|
+
result = condition.(options)
|
22
17
|
|
23
|
-
|
24
|
-
|
25
|
-
end
|
26
|
-
end
|
18
|
+
options["policy.#{@name}"] = result["policy"] # assign the policy as a skill.
|
19
|
+
options["result.policy.#{@name}"] = result
|
27
20
|
|
28
|
-
|
29
|
-
|
30
|
-
private
|
31
|
-
module Setup
|
32
|
-
def setup!(params)
|
33
|
-
evaluate_policy(super)
|
21
|
+
# flow control
|
22
|
+
result.success? # since we & this, it's only executed OnRight and the return boolean decides the direction, input is passed straight through.
|
34
23
|
end
|
35
24
|
end
|
36
|
-
include Setup
|
37
|
-
|
38
|
-
def evaluate_policy(params)
|
39
|
-
user = params[:current_user]
|
40
25
|
|
41
|
-
|
42
|
-
|
43
|
-
|
44
|
-
end
|
26
|
+
# Adds the `yield` result to the pipe and treats it like a policy-compatible object at runtime.
|
27
|
+
def self.add!(operation, import, options)
|
28
|
+
name = options[:name] || :default
|
45
29
|
|
46
|
-
|
47
|
-
|
48
|
-
end
|
30
|
+
# configure class level.
|
31
|
+
operation[path = "policy.#{name}.eval"] = yield
|
49
32
|
|
50
|
-
|
51
|
-
|
52
|
-
|
53
|
-
|
54
|
-
@policy_class, @action = policy_class, action
|
55
|
-
end
|
56
|
-
|
57
|
-
# Without a block, return the policy object (which is usually a Pundit-style class).
|
58
|
-
# When block is passed evaluate the default rule and run block when false.
|
59
|
-
def call(user, model, external_policy=nil)
|
60
|
-
policy = build_policy(user, model, external_policy)
|
61
|
-
|
62
|
-
policy.send(@action) || yield(policy, @action) if block_given?
|
63
|
-
policy
|
64
|
-
end
|
65
|
-
|
66
|
-
private
|
67
|
-
def build_policy(user, model, policy)
|
68
|
-
policy or @policy_class.new(user, model)
|
69
|
-
end
|
70
|
-
end
|
71
|
-
end
|
72
|
-
|
73
|
-
|
74
|
-
module Operation::Deny
|
75
|
-
def self.included(includer)
|
76
|
-
includer.extend ClassMethods
|
77
|
-
end
|
78
|
-
|
79
|
-
module ClassMethods
|
80
|
-
def deny!
|
81
|
-
raise NotAuthorizedError
|
82
|
-
end
|
33
|
+
# add step.
|
34
|
+
import.(:&, Eval.new( name: name, path: path ),
|
35
|
+
name: path
|
36
|
+
)
|
83
37
|
end
|
84
38
|
end
|
85
39
|
end
|
@@ -0,0 +1,19 @@
|
|
1
|
+
class Trailblazer::Operation
|
2
|
+
module Present
|
3
|
+
def self.included(includer)
|
4
|
+
includer.extend PresentMethod
|
5
|
+
includer.& Stop, before: Call
|
6
|
+
end
|
7
|
+
|
8
|
+
module PresentMethod
|
9
|
+
def present(params={}, options={}, *args)
|
10
|
+
call(params, options.merge("present.stop?" => true), *args)
|
11
|
+
end
|
12
|
+
end
|
13
|
+
end
|
14
|
+
|
15
|
+
# Stops the pipeline if "present.stop?" is set, which usually happens in Operation::present.
|
16
|
+
Present::Stop = ->(input, options) { ! options["present.stop?"] } # false returns Left.
|
17
|
+
end
|
18
|
+
|
19
|
+
# TODO: another stop for present without the contract!
|
@@ -0,0 +1,15 @@
|
|
1
|
+
module Trailblazer::Operation::Procedural
|
2
|
+
# THIS IS UNTESTED, PRIVATE API AND WILL BE REMOVED SOON.
|
3
|
+
module Contract
|
4
|
+
# Instantiate the contract, either by using the user's contract passed into #validate
|
5
|
+
# or infer the Operation contract.
|
6
|
+
def contract_for(model:self["model"], options:{}, contract_class:self["contract.default.class"])
|
7
|
+
contract!(model: model, options: options, contract_class: contract_class)
|
8
|
+
end
|
9
|
+
|
10
|
+
# Override to construct your own contract.
|
11
|
+
def contract!(model:nil, options:{}, contract_class:nil)
|
12
|
+
contract_class.new(model, options)
|
13
|
+
end
|
14
|
+
end
|
15
|
+
end
|
@@ -0,0 +1,22 @@
|
|
1
|
+
module Trailblazer::Operation::Procedural
|
2
|
+
module Validate
|
3
|
+
def validate(params, contract:self["contract.default"], path:"contract.default") # :params
|
4
|
+
# DISCUSS: should we only have path here and then look up contract ourselves?
|
5
|
+
result = validate_contract(contract, params) # run validation. # FIXME: must be overridable.
|
6
|
+
|
7
|
+
self["result.#{path}"] = result
|
8
|
+
|
9
|
+
if valid = result.success? # FIXME: to_bool or success?
|
10
|
+
yield result if block_given?
|
11
|
+
else
|
12
|
+
# self["errors.#{path}"] = result.errors # TODO: remove me
|
13
|
+
end
|
14
|
+
|
15
|
+
valid
|
16
|
+
end
|
17
|
+
|
18
|
+
def validate_contract(contract, params)
|
19
|
+
contract.(params)
|
20
|
+
end
|
21
|
+
end
|
22
|
+
end
|
@@ -0,0 +1,42 @@
|
|
1
|
+
class Trailblazer::Operation
|
2
|
+
module Policy
|
3
|
+
module Pundit
|
4
|
+
def self.import!(operation, import, policy_class, action, options={})
|
5
|
+
Policy.add!(operation, import, options) { Pundit.build(policy_class, action) }
|
6
|
+
end
|
7
|
+
|
8
|
+
def self.build(*args, &block)
|
9
|
+
Condition.new(*args, &block)
|
10
|
+
end
|
11
|
+
|
12
|
+
# Pundit::Condition is invoked at runtime when iterating the pipe.
|
13
|
+
class Condition
|
14
|
+
def initialize(policy_class, action)
|
15
|
+
@policy_class, @action = policy_class, action
|
16
|
+
end
|
17
|
+
|
18
|
+
# Instantiate the actual policy object, and call it.
|
19
|
+
def call(skills)
|
20
|
+
policy = build_policy(skills) # this translates to Pundit interface.
|
21
|
+
result!(policy.send(@action), policy)
|
22
|
+
end
|
23
|
+
|
24
|
+
private
|
25
|
+
def build_policy(skills)
|
26
|
+
@policy_class.new(skills["current_user"], skills["model"])
|
27
|
+
end
|
28
|
+
|
29
|
+
def result!(success, policy)
|
30
|
+
data = { "policy" => policy }
|
31
|
+
data["message"] = "Breach" if !success # TODO: how to allow messages here?
|
32
|
+
|
33
|
+
Result.new(success, data)
|
34
|
+
end
|
35
|
+
end
|
36
|
+
end
|
37
|
+
|
38
|
+
def self.Pundit(*args, &block)
|
39
|
+
[ Pundit, args, block ]
|
40
|
+
end
|
41
|
+
end
|
42
|
+
end
|
@@ -1,98 +1,31 @@
|
|
1
|
-
|
2
|
-
|
3
|
-
|
4
|
-
|
5
|
-
|
6
|
-
#
|
7
|
-
|
8
|
-
|
9
|
-
|
10
|
-
|
11
|
-
|
12
|
-
|
13
|
-
|
14
|
-
|
15
|
-
|
16
|
-
|
17
|
-
|
18
|
-
|
19
|
-
|
20
|
-
|
21
|
-
|
22
|
-
|
23
|
-
end
|
24
|
-
|
25
|
-
def representer_class
|
26
|
-
self._representer_class ||= infer_representer_class
|
27
|
-
end
|
28
|
-
|
29
|
-
def representer_class=(constant)
|
30
|
-
self._representer_class = constant
|
31
|
-
end
|
32
|
-
|
33
|
-
require "disposable/version"
|
34
|
-
def infer_representer_class
|
35
|
-
if Disposable::VERSION =~ /^0.1/
|
36
|
-
warn "[Trailblazer] Reform 2.0 won't be supported in Trailblazer 1.2. Don't be lazy and upgrade to Reform 2.1."
|
37
|
-
|
38
|
-
Disposable::Twin::Schema.from(contract_class,
|
39
|
-
include: [Representable::JSON],
|
40
|
-
options_from: :deserializer, # use :instance etc. in deserializer.
|
41
|
-
superclass: Representable::Decorator,
|
42
|
-
representer_from: lambda { |inline| inline.representer_class },
|
43
|
-
)
|
44
|
-
else
|
45
|
-
Disposable::Rescheme.from(contract_class,
|
46
|
-
include: [Representable::JSON],
|
47
|
-
options_from: :deserializer, # use :instance etc. in deserializer.
|
48
|
-
superclass: Representable::Decorator,
|
49
|
-
definitions_from: lambda { |inline| inline.definitions },
|
50
|
-
exclude_options: [:default, :populator], # TODO: test with populator: in an operation.
|
51
|
-
exclude_properties: [:persisted?]
|
52
|
-
)
|
1
|
+
class Trailblazer::Operation
|
2
|
+
module Representer
|
3
|
+
def self.infer(contract_class, format:Representable::JSON)
|
4
|
+
Disposable::Rescheme.from(contract_class,
|
5
|
+
include: [format],
|
6
|
+
options_from: :deserializer, # use :instance etc. in deserializer.
|
7
|
+
superclass: Representable::Decorator,
|
8
|
+
definitions_from: lambda { |inline| inline.definitions },
|
9
|
+
exclude_options: [:default, :populator], # TODO: test with populator: in an operation.
|
10
|
+
exclude_properties: [:persisted?]
|
11
|
+
)
|
12
|
+
end
|
13
|
+
|
14
|
+
module DSL
|
15
|
+
def representer(name=:default, constant=nil, &block)
|
16
|
+
heritage.record(:representer, name, constant, &block)
|
17
|
+
|
18
|
+
# FIXME: make this nicer. we want to extend same-named callback groups.
|
19
|
+
# TODO: allow the same with contract, or better, test it!
|
20
|
+
path, representer_class = Trailblazer::DSL::Build.new.({ prefix: :representer, class: representer_base_class, container: self }, name, constant, block)
|
21
|
+
|
22
|
+
self[path] = representer_class
|
53
23
|
end
|
54
|
-
end
|
55
|
-
end
|
56
|
-
|
57
|
-
private
|
58
|
-
module Rendering
|
59
|
-
# Override this if you need to pass options to the rendering.
|
60
|
-
#
|
61
|
-
# def to_json(*)
|
62
|
-
# super(include: @params[:include])
|
63
|
-
# end
|
64
|
-
def to_json(options={})
|
65
|
-
self.class.representer_class.new(represented).to_json(options)
|
66
|
-
end
|
67
|
-
|
68
|
-
# Override this if you want to render something else, e.g. the contract.
|
69
|
-
def represented
|
70
|
-
model
|
71
|
-
end
|
72
|
-
end
|
73
|
-
include Rendering
|
74
|
-
|
75
|
-
|
76
|
-
module Deserializer
|
77
|
-
module Hash
|
78
|
-
def validate_contract(params)
|
79
|
-
# use the inferred representer from the contract for deserialization in #validate.
|
80
|
-
contract.validate(params) do |document|
|
81
|
-
self.class.representer_class.new(contract).from_hash(document)
|
82
|
-
end
|
83
|
-
end
|
84
|
-
end
|
85
24
|
|
86
|
-
|
87
|
-
|
88
|
-
|
89
|
-
module JSON
|
90
|
-
def validate_contract(params)
|
91
|
-
contract.validate(params) do |document|
|
92
|
-
self.class.representer_class.new(contract).from_json(document)
|
93
|
-
end
|
25
|
+
# TODO: make engine configurable?
|
26
|
+
def representer_base_class
|
27
|
+
Class.new(Representable::Decorator) { include Representable::JSON; self }
|
94
28
|
end
|
95
29
|
end
|
96
30
|
end
|
97
|
-
include Deserializer::JSON
|
98
31
|
end
|