/
operation.rb
175 lines (136 loc) · 4.3 KB
/
operation.rb
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
require "reform"
module Trailblazer::V1_1
class Operation
require "trailblazer/1.1/operation/builder"
extend Builder # imports ::builder_class and ::build_operation.
extend Uber::InheritableAttr
inheritable_attr :contract_class
self.contract_class = Reform::Form.clone
class << self
def run(params, &block) # Endpoint behaviour
res, op = build_operation(params).run
if block_given?
yield op if res
return op
end
[res, op]
end
# Like ::run, but yield block when invalid.
def reject(*args)
res, op = run(*args)
yield op if res == false
op
end
# ::call only returns the Operation instance (or whatever was returned from #validate).
# This is useful in tests or in irb, e.g. when using Op as a factory and you already know it's valid.
def call(params)
build_operation(params, raise_on_invalid: true).run.last
end
def [](*args) # TODO: remove in 1.2.
warn "[Trailblazer] Operation[] is deprecated. Please use Operation.() and have a nice day."
call(*args)
end
# Runs #setup! but doesn't process the operation.
def present(params)
build_operation(params)
end
# This is a DSL method. Use ::contract_class and ::contract_class= for the explicit version.
# Op.contract #=> returns contract class
# Op.contract do .. end # defines contract
# Op.contract CommentForm # copies (and subclasses) external contract.
# Op.contract CommentForm do .. end # copies and extends contract.
def contract(constant=nil, &block)
return contract_class unless constant or block_given?
self.contract_class= Class.new(constant) if constant
contract_class.class_eval(&block) if block_given?
end
end
def initialize(params, options={})
@options = options
@valid = true
setup!(params) # assign/find the model
end
# Operation.run(body: "Fabulous!") #=> [true, <Comment body: "Fabulous!">]
def run
process(@params)
[valid?, self]
end
attr_reader :model
def errors
contract.errors
end
def valid?
@valid
end
def contract(*args)
contract!(*args)
end
private
module Setup
attr_writer :model
def setup!(params)
params = assign_params!(params)
setup_params!(params)
build_model!(params)
params # TODO: test me.
end
def assign_params!(params)
@params = params!(params)
end
# Overwrite #params! if you need to change its structure, by returning a new params object
# from this method.
# This is helpful if you don't want to change the original via #setup_params!.
def params!(params)
params
end
def setup_params!(params)
end
def build_model!(*args)
assign_model!(*args) # @model = ..
setup_model!(*args)
end
def assign_model!(*args)
@model = model!(*args)
end
# Implement #model! to find/create your operation model (if required).
def model!(params)
end
# Override to add attributes that can be infered from params.
def setup_model!(params)
end
end
include Setup
def validate(params, model=nil, contract_class=nil)
contract!(model, contract_class)
if @valid = validate_contract(params)
yield contract if block_given?
else
raise!(contract)
end
@valid
end
def validate_contract(params)
contract.validate(params)
end
def invalid!(result=self)
@valid = false
result
end
# When using Op.(), an invalid contract will raise an exception.
def raise!(contract)
raise InvalidContract.new(contract.errors.to_s) if @options[:raise_on_invalid]
end
# Instantiate the contract, either by using the user's contract passed into #validate
# or infer the Operation contract.
def contract_for(model=nil, contract_class=nil)
model ||= self.model
contract_class ||= self.class.contract_class
contract_class.new(model)
end
def contract!(*args)
@contract ||= contract_for(*args)
end
class InvalidContract < RuntimeError
end
end
end