trailblazer-compat 0.1.0
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +7 -0
- data/.gitignore +9 -0
- data/.travis.yml +5 -0
- data/Gemfile +7 -0
- data/README.md +76 -0
- data/Rakefile +10 -0
- data/lib/trailblazer/1.1/autoloading.rb +15 -0
- data/lib/trailblazer/1.1/endpoint.rb +31 -0
- data/lib/trailblazer/1.1/operation.rb +175 -0
- data/lib/trailblazer/1.1/operation/builder.rb +26 -0
- data/lib/trailblazer/1.1/operation/callback.rb +53 -0
- data/lib/trailblazer/1.1/operation/collection.rb +6 -0
- data/lib/trailblazer/1.1/operation/controller.rb +87 -0
- data/lib/trailblazer/1.1/operation/controller/active_record.rb +21 -0
- data/lib/trailblazer/1.1/operation/dispatch.rb +3 -0
- data/lib/trailblazer/1.1/operation/model.rb +50 -0
- data/lib/trailblazer/1.1/operation/model/active_model.rb +11 -0
- data/lib/trailblazer/1.1/operation/model/dsl.rb +29 -0
- data/lib/trailblazer/1.1/operation/model/external.rb +34 -0
- data/lib/trailblazer/1.1/operation/module.rb +29 -0
- data/lib/trailblazer/1.1/operation/policy.rb +85 -0
- data/lib/trailblazer/1.1/operation/policy/guard.rb +35 -0
- data/lib/trailblazer/1.1/operation/representer.rb +98 -0
- data/lib/trailblazer/1.1/operation/resolver.rb +30 -0
- data/lib/trailblazer/1.1/operation/responder.rb +18 -0
- data/lib/trailblazer/1.1/operation/uploaded_file.rb +77 -0
- data/lib/trailblazer/1.1/operation/worker.rb +112 -0
- data/lib/trailblazer/1.1/rails.rb +21 -0
- data/lib/trailblazer/1.1/rails/autoloading.rb +3 -0
- data/lib/trailblazer/1.1/rails/railtie.rb +24 -0
- data/lib/trailblazer/1.1/rails/test/integration.rb +6 -0
- data/lib/trailblazer/compat.rb +50 -0
- data/lib/trailblazer/compat/version.rb +5 -0
- data/trailblazer-compat.gemspec +25 -0
- metadata +118 -0
@@ -0,0 +1,21 @@
|
|
1
|
+
# Assigns an additional instance variable for +@model+ named after the model's table name (e.g. @comment).
|
2
|
+
#
|
3
|
+
# THIS MODULE IS DEPRECATED!
|
4
|
+
#
|
5
|
+
# Please don't use this module. Instead, use @model in your controller or pass the
|
6
|
+
# operation instance to a cell to present it.
|
7
|
+
module Trailblazer::V1_1::Operation::Controller::ActiveRecord
|
8
|
+
private
|
9
|
+
def setup_operation_instance_variables!(operation, options)
|
10
|
+
super
|
11
|
+
instance_variable_set(:"@#{operation_model_name}", @model)
|
12
|
+
end
|
13
|
+
|
14
|
+
def operation_model_name
|
15
|
+
# set the right variable name if collection
|
16
|
+
if @operation.is_a?(Trailblazer::Operation::Collection)
|
17
|
+
return @model.model.table_name.split(".").last
|
18
|
+
end
|
19
|
+
@model.class.table_name.split(".").last.singularize
|
20
|
+
end
|
21
|
+
end
|
@@ -0,0 +1,50 @@
|
|
1
|
+
require "trailblazer/1.1/operation/model/dsl"
|
2
|
+
|
3
|
+
module Trailblazer::V1_1
|
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
|
@@ -0,0 +1,11 @@
|
|
1
|
+
module Trailblazer::V1_1
|
2
|
+
module Operation::Model
|
3
|
+
# Automatically set model_name on operation's contract.
|
4
|
+
module ActiveModel
|
5
|
+
def contract(*, &block)
|
6
|
+
super
|
7
|
+
contract_class.model(model_class) # this assumes that Form::ActiveModel is mixed in.
|
8
|
+
end
|
9
|
+
end
|
10
|
+
end
|
11
|
+
end
|
@@ -0,0 +1,29 @@
|
|
1
|
+
class Trailblazer::V1_1::Operation
|
2
|
+
module Model
|
3
|
+
# Imports ::model and ::action into an operation.
|
4
|
+
module DSL
|
5
|
+
def self.extended(extender)
|
6
|
+
extender.extend Uber::InheritableAttr
|
7
|
+
extender.inheritable_attr :config
|
8
|
+
extender.config = {}
|
9
|
+
end
|
10
|
+
|
11
|
+
def model(name, action=nil)
|
12
|
+
self.config[:model] = name
|
13
|
+
action(action) if action # coolest line ever.
|
14
|
+
end
|
15
|
+
|
16
|
+
def action(name)
|
17
|
+
self.config[:action] = name
|
18
|
+
end
|
19
|
+
|
20
|
+
def action_name # considered private.
|
21
|
+
self.config[:action] or :create
|
22
|
+
end
|
23
|
+
|
24
|
+
def model_class # considered private.
|
25
|
+
self.config[:model] or raise "[Trailblazer] You didn't call Operation::model."
|
26
|
+
end
|
27
|
+
end
|
28
|
+
end
|
29
|
+
end
|
@@ -0,0 +1,34 @@
|
|
1
|
+
require "trailblazer/1.1/operation/model"
|
2
|
+
|
3
|
+
class Trailblazer::V1_1::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
|
@@ -0,0 +1,29 @@
|
|
1
|
+
module Trailblazer::V1_1::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
|
@@ -0,0 +1,85 @@
|
|
1
|
+
require "trailblazer/1.1/operation/policy/guard"
|
2
|
+
|
3
|
+
module Trailblazer::V1_1
|
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
|
@@ -0,0 +1,35 @@
|
|
1
|
+
module Trailblazer::V1_1
|
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
|
@@ -0,0 +1,98 @@
|
|
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::V1_1::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
|
@@ -0,0 +1,30 @@
|
|
1
|
+
require "trailblazer/1.1/operation/model/external"
|
2
|
+
require "trailblazer/1.1/operation/policy"
|
3
|
+
|
4
|
+
class Trailblazer::V1_1::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
|
@@ -0,0 +1,18 @@
|
|
1
|
+
module Trailblazer::V1_1::Operation::Responder
|
2
|
+
def self.included(base)
|
3
|
+
base.extend ClassMethods
|
4
|
+
end
|
5
|
+
|
6
|
+
module ClassMethods
|
7
|
+
def model_name
|
8
|
+
model_class.model_name
|
9
|
+
end
|
10
|
+
end
|
11
|
+
|
12
|
+
extend Forwardable
|
13
|
+
def_delegators :@model, :to_param, :destroyed?, :persisted?
|
14
|
+
|
15
|
+
def to_model
|
16
|
+
@model
|
17
|
+
end
|
18
|
+
end
|