granite 0.7.0
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +7 -0
- data/LICENSE +22 -0
- data/app/controllers/granite/controller.rb +44 -0
- data/lib/generators/USAGE +25 -0
- data/lib/generators/granite/install_controller_generator.rb +15 -0
- data/lib/generators/granite_generator.rb +32 -0
- data/lib/generators/templates/granite_action.rb.erb +22 -0
- data/lib/generators/templates/granite_action_spec.rb.erb +45 -0
- data/lib/generators/templates/granite_base_action.rb.erb +2 -0
- data/lib/generators/templates/granite_business_action.rb.erb +3 -0
- data/lib/granite.rb +24 -0
- data/lib/granite/action.rb +106 -0
- data/lib/granite/action/error.rb +14 -0
- data/lib/granite/action/performer.rb +23 -0
- data/lib/granite/action/performing.rb +132 -0
- data/lib/granite/action/policies.rb +92 -0
- data/lib/granite/action/policies/always_allow_strategy.rb +13 -0
- data/lib/granite/action/policies/any_strategy.rb +12 -0
- data/lib/granite/action/policies/required_performer_strategy.rb +14 -0
- data/lib/granite/action/preconditions.rb +107 -0
- data/lib/granite/action/preconditions/base_precondition.rb +25 -0
- data/lib/granite/action/preconditions/embedded_precondition.rb +42 -0
- data/lib/granite/action/projectors.rb +100 -0
- data/lib/granite/action/represents.rb +26 -0
- data/lib/granite/action/represents/attribute.rb +90 -0
- data/lib/granite/action/represents/reflection.rb +15 -0
- data/lib/granite/action/subject.rb +73 -0
- data/lib/granite/action/transaction.rb +40 -0
- data/lib/granite/action/translations.rb +39 -0
- data/lib/granite/action/types.rb +1 -0
- data/lib/granite/action/types/collection.rb +13 -0
- data/lib/granite/config.rb +23 -0
- data/lib/granite/context.rb +28 -0
- data/lib/granite/dispatcher.rb +64 -0
- data/lib/granite/error.rb +4 -0
- data/lib/granite/performer_proxy.rb +34 -0
- data/lib/granite/performer_proxy/proxy.rb +31 -0
- data/lib/granite/projector.rb +48 -0
- data/lib/granite/projector/controller_actions.rb +47 -0
- data/lib/granite/projector/error.rb +14 -0
- data/lib/granite/projector/helpers.rb +59 -0
- data/lib/granite/projector/translations.rb +52 -0
- data/lib/granite/projector/translations/helper.rb +22 -0
- data/lib/granite/projector/translations/view_helper.rb +12 -0
- data/lib/granite/rails.rb +11 -0
- data/lib/granite/routing.rb +4 -0
- data/lib/granite/routing/cache.rb +24 -0
- data/lib/granite/routing/caching.rb +18 -0
- data/lib/granite/routing/declarer.rb +25 -0
- data/lib/granite/routing/mapper.rb +15 -0
- data/lib/granite/routing/mapping.rb +23 -0
- data/lib/granite/routing/route.rb +29 -0
- data/lib/granite/rspec.rb +5 -0
- data/lib/granite/rspec/action_helpers.rb +8 -0
- data/lib/granite/rspec/have_projector.rb +24 -0
- data/lib/granite/rspec/projector_helpers.rb +54 -0
- data/lib/granite/rspec/raise_validation_error.rb +52 -0
- data/lib/granite/rspec/satisfy_preconditions.rb +96 -0
- metadata +338 -0
checksums.yaml
ADDED
@@ -0,0 +1,7 @@
|
|
1
|
+
---
|
2
|
+
SHA1:
|
3
|
+
metadata.gz: 171acd12e388c4b1016581a76445ff1ae6dc9051
|
4
|
+
data.tar.gz: be9fdc3c8d64c3caab3c4ee0f944cbe3892d965d
|
5
|
+
SHA512:
|
6
|
+
metadata.gz: 4773a166e6b738430fa0300894bb4889c603404a2cba4dfc2a703000de1f7cb1679742abaf9e9ec2872f4edf1b397524abc3b0bc89b7c4ca99a57e44c5c33bbd
|
7
|
+
data.tar.gz: 61a75b1ecd04c0a43c13e8b06714080fdc31bdeeda6f6b0d52fabb19d9723fbfd73bf10a5747f4306962c96c8a214910e5796c74d9fe2dc63d84cdee463c69f9
|
data/LICENSE
ADDED
@@ -0,0 +1,22 @@
|
|
1
|
+
Copyright (c) 2018 Toptal
|
2
|
+
|
3
|
+
MIT License
|
4
|
+
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining
|
6
|
+
a copy of this software and associated documentation files (the
|
7
|
+
"Software"), to deal in the Software without restriction, including
|
8
|
+
without limitation the rights to use, copy, modify, merge, publish,
|
9
|
+
distribute, sublicense, and/or sell copies of the Software, and to
|
10
|
+
permit persons to whom the Software is furnished to do so, subject to
|
11
|
+
the following conditions:
|
12
|
+
|
13
|
+
The above copyright notice and this permission notice shall be
|
14
|
+
included in all copies or substantial portions of the Software.
|
15
|
+
|
16
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
|
17
|
+
EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
|
18
|
+
MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
|
19
|
+
NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
|
20
|
+
LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
|
21
|
+
OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
|
22
|
+
WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
@@ -0,0 +1,44 @@
|
|
1
|
+
require 'granite/projector/translations/helper'
|
2
|
+
require 'action_controller'
|
3
|
+
|
4
|
+
module Granite
|
5
|
+
class Controller < Granite.base_controller_class
|
6
|
+
include Granite::Projector::Translations::Helper
|
7
|
+
|
8
|
+
singleton_class.__send__(:attr_accessor, :projector_class)
|
9
|
+
singleton_class.delegate :projector_path, :projector_name, :action_class, to: :projector_class
|
10
|
+
delegate :projector_path, :projector_name, :action_class, :projector_class, to: 'self.class'
|
11
|
+
|
12
|
+
abstract!
|
13
|
+
|
14
|
+
before_action :authorize_action!
|
15
|
+
|
16
|
+
def projector
|
17
|
+
@projector ||= begin
|
18
|
+
action_projector_class = action_class.public_send(projector_name)
|
19
|
+
if respond_to?(:projector_performer, true)
|
20
|
+
action_projector_class = action_projector_class.as(projector_performer)
|
21
|
+
end
|
22
|
+
action_projector_class.new(projector_params)
|
23
|
+
end
|
24
|
+
end
|
25
|
+
helper_method :projector
|
26
|
+
|
27
|
+
delegate :action, to: :projector
|
28
|
+
helper_method :action
|
29
|
+
|
30
|
+
def self.local_prefixes
|
31
|
+
[projector_path]
|
32
|
+
end
|
33
|
+
|
34
|
+
private
|
35
|
+
|
36
|
+
def projector_params
|
37
|
+
params
|
38
|
+
end
|
39
|
+
|
40
|
+
def authorize_action!
|
41
|
+
action.authorize!
|
42
|
+
end
|
43
|
+
end
|
44
|
+
end
|
@@ -0,0 +1,25 @@
|
|
1
|
+
Description:
|
2
|
+
Generates a sample granite action.
|
3
|
+
|
4
|
+
Example:
|
5
|
+
`rails generate granite user/create`
|
6
|
+
|
7
|
+
Will create:
|
8
|
+
apq/actions/ba/user/business_action.rb
|
9
|
+
apq/actions/ba/user/create.rb
|
10
|
+
spec/apq/actions/ba/user/create_spec.rb
|
11
|
+
|
12
|
+
`rails generate granite user/create -C`
|
13
|
+
`rails generate granite user/create --collection`
|
14
|
+
|
15
|
+
Will create:
|
16
|
+
apq/actions/ba/user/create.rb
|
17
|
+
spec/apq/actions/ba/user/create_spec.rb
|
18
|
+
|
19
|
+
`rails generate granite user/create simple`
|
20
|
+
|
21
|
+
Will create:
|
22
|
+
apq/actions/ba/user/create/simple/
|
23
|
+
apq/actions/ba/user/business_action.rb
|
24
|
+
apq/actions/ba/user/create.rb
|
25
|
+
spec/apq/actions/ba/user/create_spec.rb
|
@@ -0,0 +1,15 @@
|
|
1
|
+
require 'rails/generators/base'
|
2
|
+
|
3
|
+
module Granite
|
4
|
+
module Generators
|
5
|
+
class InstallControllerGenerator < Rails::Generators::Base
|
6
|
+
source_root File.expand_path('../../../..', __FILE__)
|
7
|
+
|
8
|
+
desc 'Creates a Granite::Controller for further customization'
|
9
|
+
|
10
|
+
def copy_controller
|
11
|
+
copy_file 'app/controllers/granite/controller.rb'
|
12
|
+
end
|
13
|
+
end
|
14
|
+
end
|
15
|
+
end
|
@@ -0,0 +1,32 @@
|
|
1
|
+
class GraniteGenerator < Rails::Generators::NamedBase
|
2
|
+
source_root File.expand_path('../templates', __FILE__)
|
3
|
+
|
4
|
+
argument :projector, type: :string, required: false
|
5
|
+
class_option :collection, type: :boolean, aliases: '-C', desc: 'Generate collection action'
|
6
|
+
|
7
|
+
def create_action
|
8
|
+
template 'granite_action.rb.erb', "apq/actions/ba/#{file_path}.rb"
|
9
|
+
template 'granite_business_action.rb.erb', "apq/actions/ba/#{class_path.join('/')}/business_action.rb" unless options.collection?
|
10
|
+
template 'granite_base_action.rb.erb', 'apq/actions/base_action.rb', skip: true
|
11
|
+
template 'granite_action_spec.rb.erb', "spec/apq/actions/ba/#{file_path}_spec.rb"
|
12
|
+
empty_directory "apq/actions/ba/#{file_path}/#{projector}" if projector
|
13
|
+
end
|
14
|
+
|
15
|
+
private
|
16
|
+
|
17
|
+
def base_class_name
|
18
|
+
if options.collection?
|
19
|
+
'BaseAction'
|
20
|
+
else
|
21
|
+
"BA::#{class_path.join('/').camelize}::BusinessAction"
|
22
|
+
end
|
23
|
+
end
|
24
|
+
|
25
|
+
def subject_name
|
26
|
+
class_path.last
|
27
|
+
end
|
28
|
+
|
29
|
+
def subject_class_name
|
30
|
+
subject_name.classify
|
31
|
+
end
|
32
|
+
end
|
@@ -0,0 +1,22 @@
|
|
1
|
+
class BA::<%= class_name %> < <%= base_class_name %>
|
2
|
+
<% if projector -%>
|
3
|
+
projector :<%= projector %>
|
4
|
+
|
5
|
+
<% end -%>
|
6
|
+
allow_if { false }
|
7
|
+
|
8
|
+
precondition do
|
9
|
+
end
|
10
|
+
<% if options.collection? -%>
|
11
|
+
|
12
|
+
def subject
|
13
|
+
@subject ||= <%= subject_class_name %>.new
|
14
|
+
end
|
15
|
+
<% end -%>
|
16
|
+
|
17
|
+
private
|
18
|
+
|
19
|
+
def execute_perform!(*)
|
20
|
+
subject.save!
|
21
|
+
end
|
22
|
+
end
|
@@ -0,0 +1,45 @@
|
|
1
|
+
require 'rails_helper'
|
2
|
+
|
3
|
+
RSpec.describe BA::<%= class_name %> do
|
4
|
+
<% if options.collection? -%>
|
5
|
+
subject(:action) { described_class.as(performer).new(attributes) }
|
6
|
+
|
7
|
+
<% else -%>
|
8
|
+
subject(:action) { described_class.as(performer).new(<%= subject_name %>, attributes) }
|
9
|
+
|
10
|
+
let(:<%= subject_name %>) { <%= subject_class_name %>.new }
|
11
|
+
<% end -%>
|
12
|
+
let(:performer) { double }
|
13
|
+
let(:attributes) { {} }
|
14
|
+
|
15
|
+
describe 'policies' do
|
16
|
+
it { is_expected.to be_allowed }
|
17
|
+
|
18
|
+
context 'when user is not authorized' do
|
19
|
+
it { is_expected.not_to be_allowed }
|
20
|
+
end
|
21
|
+
end
|
22
|
+
|
23
|
+
describe 'preconditions' do
|
24
|
+
it { is_expected.to satisfy_preconditions }
|
25
|
+
|
26
|
+
context 'when preconditions fail' do
|
27
|
+
it { is_expected.not_to satisfy_preconditions }
|
28
|
+
end
|
29
|
+
end
|
30
|
+
|
31
|
+
describe 'validations' do
|
32
|
+
end
|
33
|
+
|
34
|
+
describe '#perform!' do
|
35
|
+
<% if options.collection? -%>
|
36
|
+
specify do
|
37
|
+
expect { perform! }.to change { <%= subject_class_name %>.count }.by(1)
|
38
|
+
end
|
39
|
+
<% else -%>
|
40
|
+
specify do
|
41
|
+
expect { perform!(<%= subject_name %>) }.to change { <%= subject_name %>.reload.attributes }.to(attributes)
|
42
|
+
end
|
43
|
+
<% end -%>
|
44
|
+
end
|
45
|
+
end
|
data/lib/granite.rb
ADDED
@@ -0,0 +1,24 @@
|
|
1
|
+
require 'active_support/dependencies'
|
2
|
+
require 'action_controller'
|
3
|
+
|
4
|
+
require 'granite/config'
|
5
|
+
require 'granite/context'
|
6
|
+
|
7
|
+
module Granite
|
8
|
+
def self.config
|
9
|
+
Granite::Config.instance
|
10
|
+
end
|
11
|
+
|
12
|
+
def self.context
|
13
|
+
Granite::Context.instance
|
14
|
+
end
|
15
|
+
|
16
|
+
singleton_class.delegate(*Granite::Config.delegated, to: :config)
|
17
|
+
singleton_class.delegate(*Granite::Context.delegated, to: :context)
|
18
|
+
end
|
19
|
+
|
20
|
+
require 'granite/dispatcher'
|
21
|
+
require 'granite/action'
|
22
|
+
require 'granite/projector'
|
23
|
+
require 'granite/routing'
|
24
|
+
require 'granite/rails' if defined?(::Rails)
|
@@ -0,0 +1,106 @@
|
|
1
|
+
require 'active_data'
|
2
|
+
require 'active_record/errors'
|
3
|
+
require 'active_record/validations'
|
4
|
+
require 'active_support/callbacks'
|
5
|
+
|
6
|
+
require 'granite/action/types'
|
7
|
+
require 'granite/action/represents'
|
8
|
+
require 'granite/action/error'
|
9
|
+
require 'granite/action/performing'
|
10
|
+
require 'granite/action/performer'
|
11
|
+
require 'granite/action/preconditions'
|
12
|
+
require 'granite/action/policies'
|
13
|
+
require 'granite/action/projectors'
|
14
|
+
require 'granite/action/translations'
|
15
|
+
require 'granite/action/subject'
|
16
|
+
|
17
|
+
module Granite
|
18
|
+
class Action
|
19
|
+
class ValidationError < Error
|
20
|
+
delegate :errors, to: :action
|
21
|
+
|
22
|
+
def initialize(action)
|
23
|
+
errors = action.errors.full_messages.join(', ')
|
24
|
+
super(I18n.t(:"#{action.class.i18n_scope}.errors.messages.action_invalid", action: action.class, errors: errors, default: :'errors.messages.action_invalid'), action)
|
25
|
+
end
|
26
|
+
end
|
27
|
+
|
28
|
+
# We are using a lot of stacked additional logic for `assign_attributes`
|
29
|
+
# At least, represented and nested attributes modules in ActiveData
|
30
|
+
# are having such a method redefiniions. Both are prepended to the
|
31
|
+
# Granite action, so we have to prepend our patch as well in order
|
32
|
+
# to put it above all other, so it will handle the attributes first.
|
33
|
+
module AssignAttributes
|
34
|
+
def assign_attributes(attributes)
|
35
|
+
attributes = attributes.to_unsafe_hash if attributes.respond_to?(:to_unsafe_hash)
|
36
|
+
attributes = attributes.stringify_keys
|
37
|
+
if attributes.key?(model_name.param_key)
|
38
|
+
attributes = attributes.merge(attributes.delete(model_name.param_key))
|
39
|
+
end
|
40
|
+
super(attributes)
|
41
|
+
end
|
42
|
+
end
|
43
|
+
|
44
|
+
include ActiveSupport::Callbacks
|
45
|
+
include ActiveData::Model
|
46
|
+
include ActiveData::Model::Representation
|
47
|
+
include ActiveData::Model::Associations
|
48
|
+
include ActiveData::Model::Dirty
|
49
|
+
include ActiveModel::Validations::Callbacks
|
50
|
+
include Performing
|
51
|
+
include Subject
|
52
|
+
include Performer
|
53
|
+
include Preconditions
|
54
|
+
include Policies
|
55
|
+
include Projectors
|
56
|
+
include Translations
|
57
|
+
include Represents
|
58
|
+
prepend AssignAttributes
|
59
|
+
|
60
|
+
handle_exception ActiveRecord::RecordInvalid do |e|
|
61
|
+
errors.messages.deep_merge!(e.record.errors.messages) do |_, this, other|
|
62
|
+
(this + other).uniq
|
63
|
+
end
|
64
|
+
end
|
65
|
+
|
66
|
+
handle_exception ActiveData::ValidationError do |e|
|
67
|
+
errors.messages.deep_merge!(e.model.errors.messages) do |_, this, other|
|
68
|
+
(this + other).uniq
|
69
|
+
end
|
70
|
+
end
|
71
|
+
|
72
|
+
handle_exception Granite::Action::ValidationError do |e|
|
73
|
+
errors.messages.deep_merge!(e.action.errors.messages) do |_, this, other|
|
74
|
+
(this + other).uniq
|
75
|
+
end
|
76
|
+
end
|
77
|
+
|
78
|
+
def self.i18n_scope
|
79
|
+
:granite_action
|
80
|
+
end
|
81
|
+
|
82
|
+
# Almost the same as Dirty `#changed?` method, but
|
83
|
+
# doesn't check subject reference key
|
84
|
+
def attributes_changed?(except: [])
|
85
|
+
except = Array.wrap(except).push(self.class.reflect_on_association(:subject).reference_key)
|
86
|
+
changed_attributes.except(*except).present?
|
87
|
+
end
|
88
|
+
|
89
|
+
# Check if action is allowed to execute by current performer (see {Granite.performer})
|
90
|
+
# and satisfy all defined preconditions
|
91
|
+
#
|
92
|
+
# @return [Boolean] whether action is performable
|
93
|
+
def performable?
|
94
|
+
unless instance_variable_defined?(:@performable)
|
95
|
+
@performable = allowed? && satisfy_preconditions?
|
96
|
+
end
|
97
|
+
@performable
|
98
|
+
end
|
99
|
+
|
100
|
+
protected
|
101
|
+
|
102
|
+
def raise_validation_error(original_error = nil)
|
103
|
+
fail ValidationError, self, original_error&.backtrace
|
104
|
+
end
|
105
|
+
end
|
106
|
+
end
|
@@ -0,0 +1,23 @@
|
|
1
|
+
require 'granite/performer_proxy'
|
2
|
+
|
3
|
+
module Granite
|
4
|
+
class Action
|
5
|
+
# Performer module is responsible for setting performer for action.
|
6
|
+
#
|
7
|
+
module Performer
|
8
|
+
extend ActiveSupport::Concern
|
9
|
+
|
10
|
+
included do
|
11
|
+
include PerformerProxy
|
12
|
+
attr_reader :performer
|
13
|
+
end
|
14
|
+
|
15
|
+
def initialize(*args)
|
16
|
+
@performer = self.class.proxy_performer
|
17
|
+
super
|
18
|
+
end
|
19
|
+
|
20
|
+
delegate :id, to: :performer, prefix: true, allow_nil: true
|
21
|
+
end
|
22
|
+
end
|
23
|
+
end
|
@@ -0,0 +1,132 @@
|
|
1
|
+
require 'granite/action/transaction'
|
2
|
+
require 'granite/action/error'
|
3
|
+
|
4
|
+
module Granite
|
5
|
+
class Action
|
6
|
+
# Performing module used for defining perform procedure and error
|
7
|
+
# handling. Perform procedure is defined as block, which is
|
8
|
+
# executed in action instance context so all attributes are
|
9
|
+
# available there. Actions by default are performed in silent way
|
10
|
+
# (no validation exception raised), to raise exceptions, call bang
|
11
|
+
# method {Granite::Action::Performing#perform!}
|
12
|
+
#
|
13
|
+
# Defined exceptions handlers are also executed in action
|
14
|
+
# instance context, but additionally get raised exception as
|
15
|
+
# parameter.
|
16
|
+
#
|
17
|
+
module Performing
|
18
|
+
extend ActiveSupport::Concern
|
19
|
+
|
20
|
+
include Transaction
|
21
|
+
|
22
|
+
included do
|
23
|
+
class_attribute :_exception_handlers, instance_writer: false
|
24
|
+
self._exception_handlers = {}
|
25
|
+
|
26
|
+
protected :_exception_handlers
|
27
|
+
|
28
|
+
define_callbacks :execute_perform
|
29
|
+
end
|
30
|
+
|
31
|
+
module ClassMethods
|
32
|
+
# Register default handler for exceptions thrown inside execute_perform! method.
|
33
|
+
# @param klass Exception class, could be parent class too [Class]
|
34
|
+
# @param block [Block<Exception>] with default behavior for handling specified
|
35
|
+
# type exceptions. First block argument is raised exception instance.
|
36
|
+
#
|
37
|
+
# @return [Hash<Class, Proc>] Registered handlers
|
38
|
+
def handle_exception(klass, &block)
|
39
|
+
self._exception_handlers = _exception_handlers.merge(klass => block)
|
40
|
+
end
|
41
|
+
|
42
|
+
def perform(*)
|
43
|
+
fail 'Perform block declaration was removed! Please declare `private def execute_perform!(*)` method'
|
44
|
+
end
|
45
|
+
end
|
46
|
+
|
47
|
+
# Check preconditions and validations for action and associated objects, then
|
48
|
+
# in case of valid action run defined procedure. Procedure is wrapped with
|
49
|
+
# database transaction. Returns the result of execute_perform! method execution
|
50
|
+
# or true if method execution returned false or nil
|
51
|
+
#
|
52
|
+
# @param context [Symbol] can be optionally provided to define which
|
53
|
+
# validations to test against (the context is defined on validations
|
54
|
+
# using `:on`)
|
55
|
+
# @return [Object] result of execute_perform! method execution or false in case of errors
|
56
|
+
def perform(context: nil, **options)
|
57
|
+
transactional do
|
58
|
+
valid?(context) && perform_action(options)
|
59
|
+
end
|
60
|
+
end
|
61
|
+
|
62
|
+
# Check precondition and validations for action and associated objects, then
|
63
|
+
# raise exception in case of validation errors. In other case run defined procedure.
|
64
|
+
# Procedure is wraped with database transaction. After procedure execution check for
|
65
|
+
# errors, and raise exception if any. Returns the result of execute_perform! method execution
|
66
|
+
# or true if block execution returned false or nil
|
67
|
+
#
|
68
|
+
# @param context [Symbol] can be optionally provided to define which
|
69
|
+
# validations to test against (the context is defined on validations
|
70
|
+
# using `:on`)
|
71
|
+
# @return [Object] result of execute_perform! method execution
|
72
|
+
# @raise [Granite::Action::ValidationError] Action or associated objects are invalid
|
73
|
+
# @raise [NotImplementedError] execute_perform! method was not defined yet
|
74
|
+
def perform!(context: nil, **options)
|
75
|
+
transactional do
|
76
|
+
validate!(context)
|
77
|
+
perform_action!(**options)
|
78
|
+
end
|
79
|
+
end
|
80
|
+
|
81
|
+
# Performs action if preconditions are satisfied.
|
82
|
+
#
|
83
|
+
# @param context [Symbol] can be optionally provided to define which
|
84
|
+
# validations to test against (the context is defined on validations
|
85
|
+
# using `:on`)
|
86
|
+
# @return [Object] result of execute_perform! method execution
|
87
|
+
# @raise [Granite::Action::ValidationError] Action or associated objects are invalid
|
88
|
+
# @raise [NotImplementedError] execute_perform! method was not defined yet
|
89
|
+
def try_perform!(context: nil, **options)
|
90
|
+
return unless satisfy_preconditions?
|
91
|
+
transactional do
|
92
|
+
perform!(context: context, **options)
|
93
|
+
end
|
94
|
+
end
|
95
|
+
|
96
|
+
# Checks if action was successfully performed or not
|
97
|
+
#
|
98
|
+
# @return [Boolean] whether action was successfully performed or not
|
99
|
+
def performed?
|
100
|
+
@_action_performed.present?
|
101
|
+
end
|
102
|
+
|
103
|
+
private
|
104
|
+
|
105
|
+
def perform_action(raise_errors: false, **options)
|
106
|
+
apply_association_changes!
|
107
|
+
result = run_callbacks(:execute_perform) { execute_perform!(options) }
|
108
|
+
@_action_performed = true
|
109
|
+
result || true
|
110
|
+
rescue *_exception_handlers.keys => e
|
111
|
+
handle_exception(e)
|
112
|
+
raise_validation_error(e) if raise_errors
|
113
|
+
raise Rollback
|
114
|
+
end
|
115
|
+
|
116
|
+
def perform_action!(**options)
|
117
|
+
perform_action(raise_errors: true, **options)
|
118
|
+
end
|
119
|
+
|
120
|
+
def execute_perform!(**_options)
|
121
|
+
fail NotImplementedError, "BA perform body MUST be defined for #{self}"
|
122
|
+
end
|
123
|
+
|
124
|
+
def handle_exception(e)
|
125
|
+
klass = e.class.ancestors.detect do |ancestor|
|
126
|
+
ancestor <= Exception && _exception_handlers[ancestor]
|
127
|
+
end
|
128
|
+
instance_exec(e, &_exception_handlers[klass]) if klass
|
129
|
+
end
|
130
|
+
end
|
131
|
+
end
|
132
|
+
end
|