trailblazer 1.1.1 → 2.1.0
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +5 -5
- data/.gitignore +1 -0
- data/.rubocop-https---raw-githubusercontent-com-trailblazer-meta-master-rubocop-yml +101 -0
- data/.rubocop.yml +20 -0
- data/.rubocop_todo.yml +556 -0
- data/.travis.yml +6 -7
- data/CHANGES.md +224 -0
- data/COMM-LICENSE +62 -0
- data/CONTRIBUTING.md +179 -0
- data/Gemfile +0 -10
- data/LICENSE +9 -0
- data/README.md +68 -189
- data/Rakefile +3 -3
- data/lib/trailblazer/version.rb +3 -1
- data/lib/trailblazer.rb +3 -6
- data/test/test_helper.rb +32 -3
- data/trailblazer.gemspec +11 -15
- metadata +28 -132
- data/LICENSE.txt +0 -22
- data/TODO.md +0 -11
- data/doc/Trb-The-Stack.png +0 -0
- data/doc/trb.jpg +0 -0
- 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/builder.rb +0 -26
- data/lib/trailblazer/operation/callback.rb +0 -53
- data/lib/trailblazer/operation/collection.rb +0 -6
- data/lib/trailblazer/operation/controller.rb +0 -72
- 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/model.rb +0 -50
- data/lib/trailblazer/operation/module.rb +0 -29
- data/lib/trailblazer/operation/policy/guard.rb +0 -35
- data/lib/trailblazer/operation/policy.rb +0 -85
- data/lib/trailblazer/operation/representer.rb +0 -98
- data/lib/trailblazer/operation/resolver.rb +0 -30
- data/lib/trailblazer/operation/uploaded_file.rb +0 -77
- data/lib/trailblazer/operation.rb +0 -175
- data/test/callback_test.rb +0 -104
- data/test/collection_test.rb +0 -57
- data/test/model_test.rb +0 -148
- data/test/module_test.rb +0 -93
- data/test/operation/builder_test.rb +0 -41
- data/test/operation/contract_test.rb +0 -30
- data/test/operation/controller_test.rb +0 -111
- data/test/operation/dsl/callback_test.rb +0 -118
- data/test/operation/dsl/contract_test.rb +0 -104
- data/test/operation/dsl/representer_test.rb +0 -142
- data/test/operation/external_model_test.rb +0 -71
- data/test/operation/guard_test.rb +0 -152
- data/test/operation/policy_test.rb +0 -97
- data/test/operation/reject_test.rb +0 -34
- data/test/operation/resolver_test.rb +0 -83
- data/test/operation_test.rb +0 -275
- data/test/representer_test.rb +0 -238
- data/test/rollback_test.rb +0 -47
@@ -1,34 +0,0 @@
|
|
1
|
-
require "trailblazer/operation/model"
|
2
|
-
|
3
|
-
class Trailblazer::Operation
|
4
|
-
module Model
|
5
|
-
# Builds (finds or creates) the model _before_ the operation is instantiated.
|
6
|
-
# Passes the model instance into the builder with the following signature.
|
7
|
-
#
|
8
|
-
# builds ->(model, params)
|
9
|
-
#
|
10
|
-
# The initializer will now expect you to pass the model in via options[:model]. This
|
11
|
-
# happens automatically when coming from a builder.
|
12
|
-
module External
|
13
|
-
def self.included(includer)
|
14
|
-
includer.extend Model::DSL
|
15
|
-
includer.extend Model::BuildModel
|
16
|
-
includer.extend ClassMethods
|
17
|
-
end
|
18
|
-
|
19
|
-
def assign_model!(*) # i don't like to "disable" the `@model =` like this but it's the simplest for now.
|
20
|
-
@model = @options[:model]
|
21
|
-
end
|
22
|
-
|
23
|
-
|
24
|
-
module ClassMethods
|
25
|
-
private
|
26
|
-
def build_operation(params, options={}) # TODO: merge with Resolver::build_operation.
|
27
|
-
model = model!(params)
|
28
|
-
build_operation_class(model, params). # calls builds->(model, params).
|
29
|
-
new(params, options.merge(model: model))
|
30
|
-
end
|
31
|
-
end
|
32
|
-
end
|
33
|
-
end
|
34
|
-
end
|
@@ -1,50 +0,0 @@
|
|
1
|
-
require "trailblazer/operation/model/dsl"
|
2
|
-
|
3
|
-
module Trailblazer
|
4
|
-
class Operation
|
5
|
-
# The Model module will automatically create/find models for the configured +action+.
|
6
|
-
# It adds a public +Operation#model+ reader to access the model (after performing).
|
7
|
-
module Model
|
8
|
-
def self.included(base)
|
9
|
-
base.extend DSL
|
10
|
-
end
|
11
|
-
|
12
|
-
# Methods to create the model according to class configuration and params.
|
13
|
-
module BuildModel
|
14
|
-
def model!(params)
|
15
|
-
instantiate_model(params)
|
16
|
-
end
|
17
|
-
|
18
|
-
def instantiate_model(params)
|
19
|
-
send("#{action_name}_model", params)
|
20
|
-
end
|
21
|
-
|
22
|
-
def create_model(params)
|
23
|
-
model_class.new
|
24
|
-
end
|
25
|
-
|
26
|
-
def update_model(params)
|
27
|
-
model_class.find(params[:id])
|
28
|
-
end
|
29
|
-
|
30
|
-
alias_method :find_model, :update_model
|
31
|
-
end
|
32
|
-
|
33
|
-
|
34
|
-
# #validate no longer accepts a model since this module instantiates it for you.
|
35
|
-
def validate(params, model=self.model, *args)
|
36
|
-
super(params, model, *args)
|
37
|
-
end
|
38
|
-
|
39
|
-
private
|
40
|
-
include BuildModel
|
41
|
-
|
42
|
-
def model_class
|
43
|
-
self.class.model_class
|
44
|
-
end
|
45
|
-
def action_name
|
46
|
-
self.class.action_name
|
47
|
-
end
|
48
|
-
end
|
49
|
-
end
|
50
|
-
end
|
@@ -1,29 +0,0 @@
|
|
1
|
-
module Trailblazer::Operation::Module
|
2
|
-
def self.included(base)
|
3
|
-
base.extend ClassMethods
|
4
|
-
base.extend Included
|
5
|
-
end
|
6
|
-
|
7
|
-
module Included # TODO: use representable's inheritance mechanism.
|
8
|
-
def included(base)
|
9
|
-
super
|
10
|
-
instructions.each { |cfg|
|
11
|
-
method = cfg[0]
|
12
|
-
args = cfg[1].dup
|
13
|
-
block = cfg[2]
|
14
|
-
# options = args.extract_options!.dup # we need to duplicate options has as AM::Validations messes it up later.
|
15
|
-
|
16
|
-
base.send(method, *args, &block) } # property :name, {} do .. end
|
17
|
-
end
|
18
|
-
end
|
19
|
-
|
20
|
-
module ClassMethods
|
21
|
-
def method_missing(method, *args, &block)
|
22
|
-
instructions << [method, args, block]
|
23
|
-
end
|
24
|
-
|
25
|
-
def instructions
|
26
|
-
@instructions ||= []
|
27
|
-
end
|
28
|
-
end
|
29
|
-
end
|
@@ -1,35 +0,0 @@
|
|
1
|
-
module Trailblazer
|
2
|
-
# Policy::Guard is a very simple policy implementation.
|
3
|
-
# It adds #evaluate_policy to Operation#setup! and calls whatever
|
4
|
-
# you provided to ::policy.
|
5
|
-
#
|
6
|
-
# http://trailblazer.to/gems/operation/policy.html#guard
|
7
|
-
module Operation::Policy
|
8
|
-
module Guard
|
9
|
-
def self.included(includer)
|
10
|
-
includer.extend(DSL) # Provides ::policy(CallableObject)
|
11
|
-
includer.extend(ClassMethods)
|
12
|
-
includer.send(:include, Setup)
|
13
|
-
end
|
14
|
-
|
15
|
-
module ClassMethods
|
16
|
-
def policy(callable=nil, &block)
|
17
|
-
self.policy_config = Uber::Options::Value.new(callable || block)
|
18
|
-
end
|
19
|
-
end
|
20
|
-
|
21
|
-
def evaluate_policy(params)
|
22
|
-
call_policy(params) or raise policy_exception
|
23
|
-
end
|
24
|
-
|
25
|
-
# Override if you want your own policy invocation, e.g. with more args.
|
26
|
-
def call_policy(params)
|
27
|
-
self.class.policy_config.(self, params)
|
28
|
-
end
|
29
|
-
|
30
|
-
def policy_exception
|
31
|
-
NotAuthorizedError.new
|
32
|
-
end
|
33
|
-
end
|
34
|
-
end
|
35
|
-
end
|
@@ -1,85 +0,0 @@
|
|
1
|
-
require "trailblazer/operation/policy/guard"
|
2
|
-
|
3
|
-
module Trailblazer
|
4
|
-
class NotAuthorizedError < RuntimeError
|
5
|
-
end
|
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.
|
17
|
-
end
|
18
|
-
|
19
|
-
def policy(*args, &block)
|
20
|
-
self.policy_config = permission_class.new(*args, &block)
|
21
|
-
end
|
22
|
-
|
23
|
-
def permission_class
|
24
|
-
Permission
|
25
|
-
end
|
26
|
-
end
|
27
|
-
|
28
|
-
attr_reader :policy
|
29
|
-
|
30
|
-
private
|
31
|
-
module Setup
|
32
|
-
def setup!(params)
|
33
|
-
evaluate_policy(super)
|
34
|
-
end
|
35
|
-
end
|
36
|
-
include Setup
|
37
|
-
|
38
|
-
def evaluate_policy(params)
|
39
|
-
user = params[:current_user]
|
40
|
-
|
41
|
-
@policy = self.class.policy_config.(user, model, @policy) do |policy, action|
|
42
|
-
raise policy_exception(policy, action, model)
|
43
|
-
end
|
44
|
-
end
|
45
|
-
|
46
|
-
def policy_exception(policy, action, model)
|
47
|
-
NotAuthorizedError.new(query: action, record: model, policy: policy)
|
48
|
-
end
|
49
|
-
|
50
|
-
# Encapsulate building the Policy object and calling the defined query action.
|
51
|
-
# This assumes the policy class is "pundit-style", as in Policy.new(user, model).edit?.
|
52
|
-
class Permission
|
53
|
-
def initialize(policy_class, action)
|
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
|
83
|
-
end
|
84
|
-
end
|
85
|
-
end
|
@@ -1,98 +0,0 @@
|
|
1
|
-
# Including this will change the way deserialization in #validate works.
|
2
|
-
#
|
3
|
-
# Instead of treating params as a hash and letting the form object deserialize it,
|
4
|
-
# a representer will be infered from the contract. This representer is then passed as
|
5
|
-
# deserializer into Form#validate.
|
6
|
-
#
|
7
|
-
# TODO: so far, we only support JSON, but it's two lines to change to support any kind of format.
|
8
|
-
module Trailblazer::Operation::Representer
|
9
|
-
def self.included(base)
|
10
|
-
base.extend DSL
|
11
|
-
end
|
12
|
-
|
13
|
-
module DSL
|
14
|
-
def self.extended(extender)
|
15
|
-
extender.inheritable_attr :_representer_class
|
16
|
-
end
|
17
|
-
|
18
|
-
def representer(constant=nil, &block)
|
19
|
-
return representer_class unless constant or block_given?
|
20
|
-
|
21
|
-
self.representer_class= Class.new(constant) if constant
|
22
|
-
representer_class.class_eval(&block) if block_given?
|
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
|
-
)
|
53
|
-
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
|
-
|
86
|
-
# This looks crazy, but all it does is using a Reform hook in #validate where we can use
|
87
|
-
# our own representer for deserialization. After the object graph is set up, Reform will
|
88
|
-
# run its validation without even knowing this came from JSON.
|
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
|
94
|
-
end
|
95
|
-
end
|
96
|
-
end
|
97
|
-
include Deserializer::JSON
|
98
|
-
end
|
@@ -1,30 +0,0 @@
|
|
1
|
-
require "trailblazer/operation/model/external"
|
2
|
-
require "trailblazer/operation/policy"
|
3
|
-
|
4
|
-
class Trailblazer::Operation
|
5
|
-
# Provides builds-> (model, policy, params).
|
6
|
-
module Resolver
|
7
|
-
def self.included(includer)
|
8
|
-
includer.class_eval do
|
9
|
-
include Policy # ::build_policy
|
10
|
-
include Model::External # ::build_operation_class
|
11
|
-
|
12
|
-
extend BuildOperation # ::build_operation
|
13
|
-
end
|
14
|
-
end
|
15
|
-
|
16
|
-
module BuildOperation
|
17
|
-
def build_operation(params, options={})
|
18
|
-
model = model!(params)
|
19
|
-
policy = policy_config.call(params[:current_user], model)
|
20
|
-
build_operation_class(model, policy, params).
|
21
|
-
new(params, options.merge(model: model, policy: policy))
|
22
|
-
end
|
23
|
-
end
|
24
|
-
|
25
|
-
def initialize(params, options)
|
26
|
-
@policy = options[:policy]
|
27
|
-
super
|
28
|
-
end
|
29
|
-
end
|
30
|
-
end
|
@@ -1,77 +0,0 @@
|
|
1
|
-
require 'trailblazer/operation'
|
2
|
-
require 'action_dispatch/http/upload'
|
3
|
-
require 'tempfile'
|
4
|
-
|
5
|
-
module Trailblazer
|
6
|
-
# TODO: document:
|
7
|
-
# to_hash
|
8
|
-
# from_hash
|
9
|
-
# initialize/tmp_dir
|
10
|
-
class Operation::UploadedFile
|
11
|
-
def initialize(uploaded, options={})
|
12
|
-
@uploaded = uploaded
|
13
|
-
@options = options
|
14
|
-
@tmp_dir = options[:tmp_dir]
|
15
|
-
end
|
16
|
-
|
17
|
-
def to_hash
|
18
|
-
path = persist!
|
19
|
-
|
20
|
-
hash = {
|
21
|
-
filename: @uploaded.original_filename,
|
22
|
-
type: @uploaded.content_type,
|
23
|
-
tempfile_path: path
|
24
|
-
}
|
25
|
-
|
26
|
-
cleanup!
|
27
|
-
|
28
|
-
hash
|
29
|
-
end
|
30
|
-
|
31
|
-
# Returns a ActionDispatch::Http::UploadedFile as if the upload was in the same request.
|
32
|
-
def self.from_hash(hash)
|
33
|
-
suffix = File.extname(hash[:filename])
|
34
|
-
|
35
|
-
# we need to create a Tempfile to make Http::UploadedFile work.
|
36
|
-
tmp = Tempfile.new(["bla", suffix]) # always force file suffix to avoid problems with imagemagick etc.
|
37
|
-
file = File.open(hash[:tempfile_path])# doesn't close automatically :( # fixme: introduce strategy (Tempfile:=>slow, File:=> hopefully less memory footprint)
|
38
|
-
tmp.write(file.read) # DISCUSS: We need Tempfile.new(<File>) to avoid this slow and memory-consuming mechanics.
|
39
|
-
|
40
|
-
file.close # TODO: can we test that?
|
41
|
-
File.unlink(file)
|
42
|
-
|
43
|
-
ActionDispatch::Http::UploadedFile.new(hash.merge(tempfile: tmp))
|
44
|
-
end
|
45
|
-
|
46
|
-
private
|
47
|
-
attr_reader :tmp_dir
|
48
|
-
|
49
|
-
# convert Tempfile from Rails upload into persistent "temp" file so it is available in workers.
|
50
|
-
def persist!
|
51
|
-
path = @uploaded.path # original Tempfile path (from Rails).
|
52
|
-
path = path_with_tmp_dir(path)
|
53
|
-
|
54
|
-
path = path + "_trailblazer_upload"
|
55
|
-
|
56
|
-
FileUtils.mv(@uploaded.path, path) # move Rails upload file into persistent `path`.
|
57
|
-
path
|
58
|
-
end
|
59
|
-
|
60
|
-
def path_with_tmp_dir(path)
|
61
|
-
return path unless tmp_dir # if tmp_dir set, create path in it.
|
62
|
-
|
63
|
-
@with_tmp_dir = Tempfile.new(File.basename(path), tmp_dir)
|
64
|
-
@with_tmp_dir.path # use Tempfile to create nested dirs (os-dependent.)
|
65
|
-
end
|
66
|
-
|
67
|
-
def delete!(file)
|
68
|
-
file.close
|
69
|
-
file.unlink # the Rails uploaded file is already unlinked since moved.
|
70
|
-
end
|
71
|
-
|
72
|
-
def cleanup!
|
73
|
-
delete!(@uploaded.tempfile) if @uploaded.respond_to?(:tempfile) # this is Rails' uploaded file, not sure if we need to do that. in 3.2, we don't have UploadedFile#close, yet.
|
74
|
-
delete!(@with_tmp_dir) if @with_tmp_dir # we used that file to create a tmp file path below tmp_dir.
|
75
|
-
end
|
76
|
-
end
|
77
|
-
end
|
@@ -1,175 +0,0 @@
|
|
1
|
-
require "reform"
|
2
|
-
|
3
|
-
module Trailblazer
|
4
|
-
class Operation
|
5
|
-
require "trailblazer/operation/builder"
|
6
|
-
extend Builder # imports ::builder_class and ::build_operation.
|
7
|
-
|
8
|
-
extend Uber::InheritableAttr
|
9
|
-
inheritable_attr :contract_class
|
10
|
-
self.contract_class = Reform::Form.clone
|
11
|
-
|
12
|
-
class << self
|
13
|
-
def run(params, &block) # Endpoint behaviour
|
14
|
-
res, op = build_operation(params).run
|
15
|
-
|
16
|
-
if block_given?
|
17
|
-
yield op if res
|
18
|
-
return op
|
19
|
-
end
|
20
|
-
|
21
|
-
[res, op]
|
22
|
-
end
|
23
|
-
|
24
|
-
# Like ::run, but yield block when invalid.
|
25
|
-
def reject(*args)
|
26
|
-
res, op = run(*args)
|
27
|
-
yield op if res == false
|
28
|
-
op
|
29
|
-
end
|
30
|
-
|
31
|
-
# ::call only returns the Operation instance (or whatever was returned from #validate).
|
32
|
-
# This is useful in tests or in irb, e.g. when using Op as a factory and you already know it's valid.
|
33
|
-
def call(params)
|
34
|
-
build_operation(params, raise_on_invalid: true).run.last
|
35
|
-
end
|
36
|
-
|
37
|
-
def [](*args) # TODO: remove in 1.2.
|
38
|
-
warn "[Trailblazer] Operation[] is deprecated. Please use Operation.() and have a nice day."
|
39
|
-
call(*args)
|
40
|
-
end
|
41
|
-
|
42
|
-
# Runs #setup! but doesn't process the operation.
|
43
|
-
def present(params)
|
44
|
-
build_operation(params)
|
45
|
-
end
|
46
|
-
|
47
|
-
# This is a DSL method. Use ::contract_class and ::contract_class= for the explicit version.
|
48
|
-
# Op.contract #=> returns contract class
|
49
|
-
# Op.contract do .. end # defines contract
|
50
|
-
# Op.contract CommentForm # copies (and subclasses) external contract.
|
51
|
-
# Op.contract CommentForm do .. end # copies and extends contract.
|
52
|
-
def contract(constant=nil, &block)
|
53
|
-
return contract_class unless constant or block_given?
|
54
|
-
|
55
|
-
self.contract_class= Class.new(constant) if constant
|
56
|
-
contract_class.class_eval(&block) if block_given?
|
57
|
-
end
|
58
|
-
end
|
59
|
-
|
60
|
-
|
61
|
-
def initialize(params, options={})
|
62
|
-
@options = options
|
63
|
-
@valid = true
|
64
|
-
|
65
|
-
setup!(params) # assign/find the model
|
66
|
-
end
|
67
|
-
|
68
|
-
# Operation.run(body: "Fabulous!") #=> [true, <Comment body: "Fabulous!">]
|
69
|
-
def run
|
70
|
-
process(@params)
|
71
|
-
|
72
|
-
[valid?, self]
|
73
|
-
end
|
74
|
-
|
75
|
-
attr_reader :model
|
76
|
-
|
77
|
-
def errors
|
78
|
-
contract.errors
|
79
|
-
end
|
80
|
-
|
81
|
-
def valid?
|
82
|
-
@valid
|
83
|
-
end
|
84
|
-
|
85
|
-
def contract(*args)
|
86
|
-
contract!(*args)
|
87
|
-
end
|
88
|
-
|
89
|
-
private
|
90
|
-
module Setup
|
91
|
-
attr_writer :model
|
92
|
-
|
93
|
-
def setup!(params)
|
94
|
-
params = assign_params!(params)
|
95
|
-
setup_params!(params)
|
96
|
-
build_model!(params)
|
97
|
-
params # TODO: test me.
|
98
|
-
end
|
99
|
-
|
100
|
-
def assign_params!(params)
|
101
|
-
@params = params!(params)
|
102
|
-
end
|
103
|
-
|
104
|
-
# Overwrite #params! if you need to change its structure, by returning a new params object
|
105
|
-
# from this method.
|
106
|
-
# This is helpful if you don't want to change the original via #setup_params!.
|
107
|
-
def params!(params)
|
108
|
-
params
|
109
|
-
end
|
110
|
-
|
111
|
-
def setup_params!(params)
|
112
|
-
end
|
113
|
-
|
114
|
-
def build_model!(*args)
|
115
|
-
assign_model!(*args) # @model = ..
|
116
|
-
setup_model!(*args)
|
117
|
-
end
|
118
|
-
|
119
|
-
def assign_model!(*args)
|
120
|
-
@model = model!(*args)
|
121
|
-
end
|
122
|
-
|
123
|
-
# Implement #model! to find/create your operation model (if required).
|
124
|
-
def model!(params)
|
125
|
-
end
|
126
|
-
|
127
|
-
# Override to add attributes that can be infered from params.
|
128
|
-
def setup_model!(params)
|
129
|
-
end
|
130
|
-
end
|
131
|
-
include Setup
|
132
|
-
|
133
|
-
def validate(params, model=nil, contract_class=nil)
|
134
|
-
contract!(model, contract_class)
|
135
|
-
|
136
|
-
if @valid = validate_contract(params)
|
137
|
-
yield contract if block_given?
|
138
|
-
else
|
139
|
-
raise!(contract)
|
140
|
-
end
|
141
|
-
|
142
|
-
@valid
|
143
|
-
end
|
144
|
-
|
145
|
-
def validate_contract(params)
|
146
|
-
contract.validate(params)
|
147
|
-
end
|
148
|
-
|
149
|
-
def invalid!(result=self)
|
150
|
-
@valid = false
|
151
|
-
result
|
152
|
-
end
|
153
|
-
|
154
|
-
# When using Op.(), an invalid contract will raise an exception.
|
155
|
-
def raise!(contract)
|
156
|
-
raise InvalidContract.new(contract.errors.to_s) if @options[:raise_on_invalid]
|
157
|
-
end
|
158
|
-
|
159
|
-
# Instantiate the contract, either by using the user's contract passed into #validate
|
160
|
-
# or infer the Operation contract.
|
161
|
-
def contract_for(model=nil, contract_class=nil)
|
162
|
-
model ||= self.model
|
163
|
-
contract_class ||= self.class.contract_class
|
164
|
-
|
165
|
-
contract_class.new(model)
|
166
|
-
end
|
167
|
-
|
168
|
-
def contract!(*args)
|
169
|
-
@contract ||= contract_for(*args)
|
170
|
-
end
|
171
|
-
|
172
|
-
class InvalidContract < RuntimeError
|
173
|
-
end
|
174
|
-
end
|
175
|
-
end
|