granite 0.7.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- 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
|