granite 0.9.7 → 0.11.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 CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 3b29f40ab453a5dccfe51b587a3bb8ac106441393110a34853aa7d6319a52dc8
4
- data.tar.gz: 7670225bab82074b188a5ec72222a3986da4da217f2268473f3881f5487fb3ed
3
+ metadata.gz: 7c87ec932a4c14889c6de808acc812dc2eac40bda53454aa5b737176a3b02c01
4
+ data.tar.gz: 57c47008c125ec1283bddb6e407b94be2099e1a6e02f909751de03ecbe0669c6
5
5
  SHA512:
6
- metadata.gz: 127fb08af9bb0a97162650884ed9f7b82793db3963fa236a35b9ed1c4cd859cf3a030743eac7bf25ac7845838b61da42bc588d3baad3aa5919e5a8ef495bdb14
7
- data.tar.gz: 5a9f6d3bd77cad622f2216706b46e413ed8263b960cd34eb5726c7cc3dc236910aa707acfa2e77df732cd15120be7774ff87261b3854bac8f65e1782096440a0
6
+ metadata.gz: fca3402fa5593057b236362bdb6050c30f65b1f65dc7e48699ca0179e31005f8c74b1e613a901ae2211869fc2d28ad091fd008b350e7a61c5ba5807528929f98
7
+ data.tar.gz: 604bb52620145a6b23c37ca2a8e35250504171a7ce308371c4c4e964a83d7b5ce4d971ff8841ed3f607a3041ff31a9e9ce1058b46a8335811c1ed15411fd959b
@@ -0,0 +1,16 @@
1
+ module Granite
2
+ class Controller
3
+ module Translations
4
+ def i18n_scopes
5
+ Granite::Translations.combine_paths(projector.i18n_scopes, [*action_name, nil])
6
+ end
7
+
8
+ def translate(*args, **options)
9
+ key, options = Granite::Translations.scope_translation_args(i18n_scopes, *args, **options)
10
+ super(key, **options)
11
+ end
12
+
13
+ alias t translate
14
+ end
15
+ end
16
+ end
@@ -1,9 +1,9 @@
1
- require 'granite/projector/translations/helper'
2
1
  require 'action_controller'
3
2
 
4
3
  module Granite
5
4
  class Controller < Granite.base_controller_class
6
- include Granite::Projector::Translations::Helper
5
+ include Controller::Translations
6
+ helper Controller::Translations
7
7
 
8
8
  singleton_class.__send__(:attr_accessor, :projector_class)
9
9
  singleton_class.delegate :projector_path, :projector_name, :action_class, to: :projector_class
@@ -42,7 +42,7 @@ module Granite
42
42
  # @return [Object] result of execute_perform! method execution or false in case of errors
43
43
  def perform(context: nil, **options)
44
44
  transaction do
45
- valid?(context) && perform_action(options)
45
+ valid?(context) && perform_action(**options)
46
46
  end
47
47
  end
48
48
 
@@ -94,7 +94,7 @@ module Granite
94
94
  def perform_action(raise_errors: false, **options)
95
95
  result = run_callbacks(:execute_perform) do
96
96
  apply_association_changes!
97
- execute_perform!(options)
97
+ execute_perform!(**options)
98
98
  end
99
99
  @_action_performed = true
100
100
  result || true
@@ -55,17 +55,17 @@ module Granite
55
55
  end
56
56
  end
57
57
 
58
- def try_perform!(*)
58
+ def try_perform!(*, **)
59
59
  authorize!
60
60
  super
61
61
  end
62
62
 
63
- def perform(*)
63
+ def perform(*, **)
64
64
  authorize!
65
65
  super
66
66
  end
67
67
 
68
- def perform!(*)
68
+ def perform!(*, **)
69
69
  authorize!
70
70
  super
71
71
  end
@@ -9,10 +9,7 @@ module Granite
9
9
  end
10
10
 
11
11
  def execute!(context)
12
- return if @options[:if] && !context.instance_exec(&@options[:if])
13
- return if @options[:unless] && context.instance_exec(&@options[:unless])
14
-
15
- _execute(context)
12
+ _execute(context) if context.conditions_satisfied?(**@options)
16
13
  end
17
14
 
18
15
  private
@@ -51,15 +51,7 @@ module Granite
51
51
  elsif args.first.is_a?(Class)
52
52
  add_precondition(ObjectPrecondition, *args, options)
53
53
  else
54
- common_options = options.extract!(:if, :unless, :desc, :description)
55
- args.each do |type|
56
- precondition common_options.merge(type => {})
57
- end
58
- options.each do |key, value|
59
- value = Array.wrap(value)
60
- precondition_options = value.extract_options!
61
- add_precondition(klass(key), *value, precondition_options.merge!(common_options))
62
- end
54
+ add_preconditions_hash(*args, **options)
63
55
  end
64
56
  end
65
57
 
@@ -72,6 +64,18 @@ module Granite
72
64
  end || fail(NameError, "No precondition class for #{key}Precondition")
73
65
  end
74
66
 
67
+ def add_preconditions_hash(*args, **options)
68
+ common_options = options.extract!(:if, :unless, :desc, :description)
69
+ args.each do |type|
70
+ precondition common_options.merge(type => {})
71
+ end
72
+ options.each do |key, value|
73
+ value = Array.wrap(value)
74
+ precondition_options = value.extract_options!
75
+ add_precondition(klass(key), *value, precondition_options.merge!(common_options))
76
+ end
77
+ end
78
+
75
79
  def add_precondition(klass, *args, &block)
76
80
  self._preconditions += klass.new(*args, &block)
77
81
  end
@@ -0,0 +1,25 @@
1
+ module Granite
2
+ class Action
3
+ module Translations
4
+ extend ActiveSupport::Concern
5
+
6
+ module ClassMethods
7
+ def i18n_scope
8
+ :granite_action
9
+ end
10
+
11
+ def i18n_scopes
12
+ lookup_ancestors.flat_map do |klass|
13
+ :"#{klass.i18n_scope}.#{klass.model_name.i18n_key}"
14
+ end + [nil]
15
+ end
16
+ end
17
+
18
+ def translate(*args, **options)
19
+ key, options = Granite::Translations.scope_translation_args(self.class.i18n_scopes, *args, **options)
20
+ I18n.translate(key, **options)
21
+ end
22
+ alias t translate
23
+ end
24
+ end
25
+ end
@@ -12,6 +12,7 @@ require 'granite/action/preconditions'
12
12
  require 'granite/action/policies'
13
13
  require 'granite/action/projectors'
14
14
  require 'granite/action/subject'
15
+ require 'granite/action/translations'
15
16
 
16
17
  module Granite
17
18
  class Action
@@ -39,6 +40,7 @@ module Granite
39
40
  end
40
41
 
41
42
  include Base
43
+ include Translations
42
44
  include Performing
43
45
  include Subject
44
46
  include Performer
@@ -65,10 +67,6 @@ module Granite
65
67
  end
66
68
  end
67
69
 
68
- def self.i18n_scope
69
- :granite_action
70
- end
71
-
72
70
  # Almost the same as Dirty `#changed?` method, but
73
71
  # doesn't check subject reference key
74
72
  def attributes_changed?(except: [])
@@ -0,0 +1,38 @@
1
+ module Granite
2
+ module AssignData
3
+ DataAssignment = Struct.new(:method, :options)
4
+
5
+ extend ActiveSupport::Concern
6
+
7
+ included do
8
+ class_attribute :data_assignments
9
+ self.data_assignments = []
10
+
11
+ alias_method :only_run_validations!, :run_validations!
12
+ protected :only_run_validations! # rubocop:disable Style/AccessModifierDeclarations
13
+ end
14
+
15
+ module ClassMethods
16
+ # Defines a callback to call when assigning data from business action to model.
17
+ # @param methods [Array<Symbol>] list of methods to call
18
+ # @param block [Proc] a block to call
19
+ # @option options [Symbol, Proc, Object] :if call methods/block if this condition evaluates to true
20
+ # @option options [Symbol, Proc, Object] :unless call method/block unless this condition evaluates to true
21
+ def assign_data(*methods, **options, &block)
22
+ self.data_assignments += methods.map { |method| DataAssignment.new(method, options) }
23
+ self.data_assignments += [DataAssignment.new(block, options)] if block
24
+ end
25
+ end
26
+
27
+ private
28
+
29
+ def run_validations!
30
+ assign_data
31
+ super
32
+ end
33
+
34
+ def assign_data
35
+ data_assignments.each { |assignment| evaluate(assignment.method) if conditions_satisfied?(**assignment.options) }
36
+ end
37
+ end
38
+ end
data/lib/granite/base.rb CHANGED
@@ -5,8 +5,11 @@ require 'active_data/model/associations'
5
5
 
6
6
  require 'granite/translations'
7
7
  require 'granite/represents'
8
+ require 'granite/assign_data'
8
9
 
9
10
  module Granite
11
+ # Base included in Granite::Action, but also used by ActiveData when building data objects (e.g. when using
12
+ # embeds_many)
10
13
  module Base
11
14
  extend ActiveSupport::Concern
12
15
 
@@ -18,7 +21,8 @@ module Granite
18
21
  include ActiveData::Model::Primary
19
22
  include ActiveModel::Validations::Callbacks
20
23
 
21
- include Granite::Translations
24
+ include Granite::Util
25
+ include Granite::AssignData
22
26
  include Granite::Represents
23
27
  end
24
28
  end
@@ -25,7 +25,7 @@ class Granite::Dispatcher
25
25
  end
26
26
 
27
27
  def controller(params, *_args)
28
- projector(params.slice(:granite_action, :granite_projector).symbolize_keys)&.controller_class
28
+ projector(*params.values_at(:granite_action, :granite_projector))&.controller_class
29
29
  end
30
30
 
31
31
  def prepare_params!(params, *_args)
@@ -39,19 +39,19 @@ class Granite::Dispatcher
39
39
  controller(req.params),
40
40
  action_name(
41
41
  req.request_method_symbol,
42
- req.params.slice(:granite_action, :granite_projector, :projector_action).symbolize_keys
42
+ *req.params.values_at(:granite_action, :granite_projector, :projector_action)
43
43
  )
44
44
  ]
45
45
  end
46
46
 
47
- memoize def action_name(request_method_symbol, granite_action:, granite_projector:, projector_action: '')
48
- projector = projector(granite_action: granite_action, granite_projector: granite_projector)
47
+ memoize def action_name(request_method_symbol, granite_action, granite_projector, projector_action)
48
+ projector = projector(granite_action, granite_projector)
49
49
  return unless projector
50
50
 
51
- projector.action_for(request_method_symbol, projector_action)
51
+ projector.action_for(request_method_symbol, projector_action.to_s)
52
52
  end
53
53
 
54
- memoize def projector(granite_action:, granite_projector:)
54
+ memoize def projector(granite_action, granite_projector)
55
55
  action = business_action(granite_action)
56
56
 
57
57
  action.public_send(granite_projector) if action.respond_to?(granite_projector)
@@ -23,11 +23,11 @@ module Granite
23
23
  controller_class.__send__(:define_method, name, &block)
24
24
  class_eval <<-METHOD, __FILE__, __LINE__ + 1
25
25
  def #{name}_url(options = {})
26
- action_url(:#{name}, options.symbolize_keys)
26
+ action_url(:#{name}, **options.symbolize_keys)
27
27
  end
28
28
 
29
29
  def #{name}_path(options = {})
30
- action_path(:#{name}, options.symbolize_keys)
30
+ action_path(:#{name}, **options.symbolize_keys)
31
31
  end
32
32
  METHOD
33
33
  else
@@ -1,52 +1,18 @@
1
1
  module Granite
2
2
  class Projector
3
3
  module Translations
4
- extend ActiveSupport::Concern
4
+ include ActionView::Helpers::TranslationHelper
5
5
 
6
- class TranslationsWrapper
7
- include ActionView::Helpers::TranslationHelper
6
+ def i18n_scopes
7
+ Granite::Translations.combine_paths(action_class.i18n_scopes, [:"#{projector_name}"])
8
8
  end
9
9
 
10
- def translate(*args)
11
- TranslationsWrapper.new.translate(*self.class.scope_translation_args_by_projector(args))
10
+ def translate(*args, **options)
11
+ key, options = Granite::Translations.scope_translation_args(i18n_scopes, *args, **options)
12
+ super(key, **options)
12
13
  end
13
- alias t translate
14
-
15
- module ClassMethods
16
- def scope_translation_args_by_projector(args, action_name: nil)
17
- options = args.extract_options!
18
-
19
- lookups = expand_relative_key(args.first, action_name).map(&:to_sym)
20
- lookups += [options[:default]]
21
- lookups = lookups.flatten.compact
22
-
23
- key = lookups.shift
24
- options[:default] = lookups
25
-
26
- [key, options]
27
- end
28
-
29
- private
30
14
 
31
- def expand_relative_key(key, action_name = nil)
32
- return [key] unless key.is_a?(String) && key.start_with?('.')
33
-
34
- base_keys = extract_base_keys(key, action_name)
35
-
36
- action_class.lookup_ancestors.map do |klass|
37
- base_keys.map do |base_key|
38
- :"#{klass.i18n_scope}.#{klass.model_name.i18n_key}.#{base_key}"
39
- end
40
- end.flatten + base_keys
41
- end
42
-
43
- def extract_base_keys(key, action_name)
44
- undotted_key = key.sub(/^\./, '')
45
- base_keys = [:"#{projector_name}.#{undotted_key}"]
46
- base_keys.unshift :"#{projector_name}.#{action_name}.#{undotted_key}" if action_name
47
- base_keys
48
- end
49
- end
15
+ alias t translate
50
16
  end
51
17
  end
52
18
  end
@@ -13,10 +13,7 @@ module Granite
13
13
  fields.each do |field|
14
14
  add_attribute Granite::Represents::Reflection, field, options, &block
15
15
 
16
- before_validation do
17
- attribute(field).sync if attribute(field).changed?
18
- true
19
- end
16
+ assign_data { attribute(field).sync if attribute(field).changed? }
20
17
  end
21
18
  end
22
19
  end
@@ -5,8 +5,8 @@ module Granite
5
5
  module Routing
6
6
  module Mapper
7
7
  def granite(projector_path, **options)
8
- route = Route.new(projector_path, options.extract!(:path, :as, :projector_prefix))
9
- Declarer.declare(self, route, options)
8
+ route = Route.new(projector_path, **options.extract!(:path, :as, :projector_prefix))
9
+ Declarer.declare(self, route, **options)
10
10
  end
11
11
  end
12
12
  end
@@ -1,19 +1,14 @@
1
1
  module Granite
2
- module Translations
3
- extend ActiveSupport::Concern
4
-
5
- def translate(*args)
6
- I18n.translate(*self.class.scope_translation_args(args))
7
- end
8
- alias t translate
9
-
10
- module ClassMethods
11
- def scope_translation_args(args)
12
- options = args.extract_options!
2
+ class Translations
3
+ class << self
4
+ def combine_paths(paths1, paths2)
5
+ paths1.flat_map do |path1|
6
+ paths2.map { |path2| [*path1, *path2].join('.') }
7
+ end
8
+ end
13
9
 
14
- lookups = expand_relative_key(args.first).map(&:to_sym)
15
- lookups += [options[:default]]
16
- lookups = lookups.flatten.compact
10
+ def scope_translation_args(scopes, key, *, **options)
11
+ lookups = expand_relative_key(scopes, key) + Array(options[:default])
17
12
 
18
13
  key = lookups.shift
19
14
  options[:default] = lookups
@@ -23,14 +18,10 @@ module Granite
23
18
 
24
19
  private
25
20
 
26
- def expand_relative_key(key)
21
+ def expand_relative_key(scopes, key)
27
22
  return [key] unless key.is_a?(String) && key.start_with?('.')
28
23
 
29
- base_key = key.sub(/^\./, '')
30
-
31
- lookup_ancestors.map do |klass|
32
- :"#{klass.i18n_scope}.#{klass.model_name.i18n_key}.#{base_key}"
33
- end.flatten + [base_key]
24
+ combine_paths(scopes, [key.sub(/^\./, '')]).map(&:to_sym)
34
25
  end
35
26
  end
36
27
  end
@@ -0,0 +1,51 @@
1
+ module Granite
2
+ module Util
3
+ extend ActiveSupport::Concern
4
+
5
+ # Evaluates value and returns result based on what was passed:
6
+ # - if Proc was passed, then executes it in context of self
7
+ # - if Symbol was passed, then calls a method with that name and returns result
8
+ # - otherwise just returns the value itself
9
+ # @param value [Object] value to evaluate
10
+ # @return [Object] result of evaluation
11
+ def evaluate(value)
12
+ case value
13
+ when Proc
14
+ evaluate_proc(value)
15
+ when Symbol
16
+ evaluate_symbol(value)
17
+ else
18
+ value
19
+ end
20
+ end
21
+
22
+ # Evaluates `if` or `unless` conditions present in the supplied
23
+ # `options` being it a symbol or callable.
24
+ #
25
+ # @param [Hash] options The method options to evaluate.
26
+ # @option options :if method name or callable
27
+ # @option options :unless method name or callable
28
+ # @return [Boolean] whether conditions are satisfied
29
+ def conditions_satisfied?(**options)
30
+ fail ArgumentError, 'You cannot specify both if and unless' if options.key?(:if) && options.key?(:unless)
31
+
32
+ if options.key?(:if)
33
+ evaluate(options[:if])
34
+ elsif options.key?(:unless)
35
+ !evaluate(options[:unless])
36
+ else
37
+ true
38
+ end
39
+ end
40
+
41
+ private
42
+
43
+ def evaluate_proc(value)
44
+ instance_exec(&value)
45
+ end
46
+
47
+ def evaluate_symbol(value)
48
+ __send__(value)
49
+ end
50
+ end
51
+ end
@@ -1,3 +1,3 @@
1
1
  module Granite
2
- VERSION = '0.9.7'.freeze
2
+ VERSION = '0.11.0'.freeze
3
3
  end
data/lib/granite.rb CHANGED
@@ -4,6 +4,7 @@ require 'action_controller'
4
4
  require 'granite/version'
5
5
  require 'granite/config'
6
6
  require 'granite/context'
7
+ require 'granite/util'
7
8
 
8
9
  module Granite
9
10
  def self.config
metadata CHANGED
@@ -1,14 +1,14 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: granite
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.9.7
4
+ version: 0.11.0
5
5
  platform: ruby
6
6
  authors:
7
- - Arkadiy Zabazhanov & friends
7
+ - Toptal Engineering
8
8
  autorequire:
9
9
  bindir: bin
10
10
  cert_chain: []
11
- date: 2021-01-22 00:00:00.000000000 Z
11
+ date: 2021-08-27 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: actionpack
@@ -302,6 +302,7 @@ extra_rdoc_files: []
302
302
  files:
303
303
  - LICENSE
304
304
  - app/controllers/granite/controller.rb
305
+ - app/controllers/granite/controller/translations.rb
305
306
  - config/rubocop-default.yml
306
307
  - lib/generators/USAGE
307
308
  - lib/generators/granite/install_controller_generator.rb
@@ -330,8 +331,10 @@ files:
330
331
  - lib/granite/action/transaction.rb
331
332
  - lib/granite/action/transaction_manager.rb
332
333
  - lib/granite/action/transaction_manager/transactions_stack.rb
334
+ - lib/granite/action/translations.rb
333
335
  - lib/granite/action/types.rb
334
336
  - lib/granite/action/types/collection.rb
337
+ - lib/granite/assign_data.rb
335
338
  - lib/granite/base.rb
336
339
  - lib/granite/config.rb
337
340
  - lib/granite/context.rb
@@ -344,8 +347,6 @@ files:
344
347
  - lib/granite/projector/error.rb
345
348
  - lib/granite/projector/helpers.rb
346
349
  - lib/granite/projector/translations.rb
347
- - lib/granite/projector/translations/helper.rb
348
- - lib/granite/projector/translations/view_helper.rb
349
350
  - lib/granite/rails.rb
350
351
  - lib/granite/represents.rb
351
352
  - lib/granite/represents/attribute.rb
@@ -365,6 +366,7 @@ files:
365
366
  - lib/granite/rspec/satisfy_preconditions.rb
366
367
  - lib/granite/translations.rb
367
368
  - lib/granite/typecasters.rb
369
+ - lib/granite/util.rb
368
370
  - lib/granite/version.rb
369
371
  - lib/rubocop-granite.rb
370
372
  - lib/rubocop/granite.rb
@@ -1,22 +0,0 @@
1
- require 'granite/projector/translations/view_helper'
2
-
3
- module Granite
4
- class Projector
5
- module Translations
6
- module Helper
7
- extend ActiveSupport::Concern
8
-
9
- included do
10
- delegate :scope_translation_args_by_projector, to: :projector_class
11
- helper_method :scope_translation_args_by_projector
12
- helper ViewHelper
13
- end
14
-
15
- def translate(*args)
16
- super(*scope_translation_args_by_projector(args, action_name: action_name))
17
- end
18
- alias t translate
19
- end
20
- end
21
- end
22
- end
@@ -1,12 +0,0 @@
1
- module Granite
2
- class Projector
3
- module Translations
4
- module ViewHelper
5
- def translate(*args)
6
- super(*scope_translation_args_by_projector(args, action_name: action_name))
7
- end
8
- alias t translate
9
- end
10
- end
11
- end
12
- end