mime_actor 0.3.2 → 0.4.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: b52e3908f3269eb87dbe77d561e9bdb9067446b142e526ea8198fbe6481f29c4
4
- data.tar.gz: 5d89ea4e185dfefcbbc1576a63967d1d0403f2dbcc39fe20277ee5a3194d9f33
3
+ metadata.gz: 7f099315434127b3b7044aad85eca2f69664521753dd339af0f8b6829e0ef8dc
4
+ data.tar.gz: f8d79ab0813b5f3d63a8a991c9a0f39f328cc67e991738a21030a7fcaee3eae6
5
5
  SHA512:
6
- metadata.gz: 23e504b9e30c8584bf7e7d65d4e6e4033bb92ad2930743e1dc6c91e2bb92226c318ba25a964d4f37cb8c7c356c2766ce5f9215909f6118d10d71b65d13288854
7
- data.tar.gz: e4b8a9071b709ab56ac88f9b1605a1868be2952096517922388186a03ccdfe7ac8aba36b1d114e8a09a66cdc23e89bd6bea8a0adba3b600234ffdc758c9aa038
6
+ metadata.gz: ba11f403b7031051f9d56577a34dc7b2c63906aabe7b6e663fa9ef87225e0a9334d3a9f08eabbefc5f85a201c8ed73e231e4c616a2f0bac7d1a37e05607a4e81
7
+ data.tar.gz: 3f21642e23e14ef5a175740e7916618ed3fb99b8395197d7b5239abe0ead83d0ea498ed1b6f63fa4830e9803e2bc16c04f32c43b25544bcf57c9986f2999f528
data/.rubocop.yml CHANGED
@@ -15,6 +15,19 @@ Style/BlockComments:
15
15
  Exclude:
16
16
  - spec/*_helper.rb
17
17
 
18
+ Metrics/AbcSize:
19
+ Enabled: false
20
+
21
+ Metrics/CyclomaticComplexity:
22
+ Enabled: false
23
+
24
+ Metrics/PerceivedComplexity:
25
+ Enabled: false
26
+
27
+ Metrics/MethodLength:
28
+ CountAsOne: ['array', 'heredoc', 'method_call']
29
+ Max: 15
30
+
18
31
  Style/StringLiterals:
19
32
  EnforcedStyle: double_quotes
20
33
 
@@ -32,7 +45,14 @@ Layout/HashAlignment:
32
45
  EnforcedColonStyle: table
33
46
 
34
47
  RSpec/MultipleExpectations:
35
- Enabled: false
48
+ Max: 6
36
49
 
37
50
  RSpec/ExampleLength:
38
- Enabled: false
51
+ CountAsOne: ['array', 'heredoc', 'method_call']
52
+ Max: 10
53
+
54
+ RSpec/MultipleMemoizedHelpers:
55
+ Max: 10
56
+
57
+ RSpec/NestedGroups:
58
+ Max: 5
data/README.md CHANGED
@@ -1,6 +1,6 @@
1
1
  # mime_actor
2
2
 
3
- [![Gem Version](https://badge.fury.io/rb/mime_actor.svg)](https://badge.fury.io/rb/mime_actor)
3
+ [![Gem Version](https://badge.fury.io/rb/mime_actor.png)](https://badge.fury.io/rb/mime_actor)
4
4
  [![Build](https://github.com/ryancyq/mime_actor/actions/workflows/build.yml/badge.svg)](https://github.com/ryancyq/mime_actor/actions/workflows/build.yml)
5
5
 
6
6
  Action rendering and rescue with mime type via configuration in actionpack
@@ -5,6 +5,7 @@ require "mime_actor/stage"
5
5
  require "mime_actor/rescue"
6
6
 
7
7
  require "active_support/concern"
8
+ require "active_support/lazy_load_hooks"
8
9
  require "abstract_controller/rendering"
9
10
  require "action_controller/metal/mime_responds"
10
11
 
@@ -14,40 +15,31 @@ module MimeActor
14
15
 
15
16
  include AbstractController::Rendering # required by MimeResponds
16
17
  include ActionController::MimeResponds
18
+
17
19
  include Scene
18
20
  include Stage
19
21
  include Rescue
22
+ include Logging
20
23
 
21
- module ClassMethods
22
- def dispatch_act(action: nil, format: nil, context: self, &block)
23
- lambda do
24
- context.instance_exec(&block)
25
- rescue StandardError => e
26
- (respond_to?(:rescue_actor) && rescue_actor(e, action:, format:, context:)) || raise
27
- end
28
- end
29
- end
30
-
31
- private
32
-
33
- def play_scene(action)
24
+ def start_scene(action)
34
25
  action = action&.to_sym
35
- return unless acting_scenes.key?(action)
26
+ formats = acting_scenes.fetch(action, Set.new)
27
+
28
+ if formats.empty?
29
+ logger.warn { "format is empty, action: #{action}" }
30
+ return
31
+ end
36
32
 
37
- mime_types = acting_scenes.fetch(action, Set.new)
38
33
  respond_to do |collector|
39
- mime_types.each do |mime_type|
40
- next unless (actor = find_actor("#{action}_#{mime_type}"))
41
-
42
- dispatch = self.class.dispatch_act(
43
- action: action,
44
- format: mime_type,
45
- context: self,
46
- &actor
47
- )
48
- collector.public_send(mime_type, &dispatch)
34
+ formats.each do |format|
35
+ dispatch = self.class.dispatch_cue(action: action, format: format, context: self) do
36
+ cue_actor("#{action}_#{format}")
37
+ end
38
+ collector.public_send(format, &dispatch)
49
39
  end
50
40
  end
51
41
  end
42
+
43
+ ActiveSupport.run_load_hooks(:mime_actor, self)
52
44
  end
53
45
  end
@@ -1,20 +1,45 @@
1
1
  # frozen_string_literal: true
2
2
 
3
+ require "set"
4
+
3
5
  module MimeActor
4
6
  class Error < StandardError
7
+ def inspect
8
+ "<#{self.class.name}> #{message}"
9
+ end
10
+
11
+ def generate_message
12
+ self.class.name
13
+ end
5
14
  end
6
15
 
7
- class ActorNotFound < Error
16
+ class ActorError < Error
8
17
  attr_reader :actor
9
18
 
10
19
  def initialize(actor)
11
20
  @actor = actor
21
+ super(generate_message)
22
+ end
23
+ end
12
24
 
13
- super(":#{actor} not found")
25
+ class ActorNotFound < ActorError
26
+ def generate_message
27
+ ":#{actor} not found"
14
28
  end
29
+ end
15
30
 
16
- def inspect
17
- "<#{self.class.name}> #{message}"
31
+ class ActionError < Error
32
+ attr_reader :action
33
+
34
+ def initialize(action = nil)
35
+ @action = action
36
+ super(generate_message)
37
+ end
38
+ end
39
+
40
+ class ActionExisted < ActionError
41
+ def generate_message
42
+ "action :#{action} already existed"
18
43
  end
19
44
  end
20
45
  end
@@ -0,0 +1,29 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "active_support/concern"
4
+ require "active_support/configurable"
5
+ require "active_support/isolated_execution_state" # required by active_support/logger
6
+ require "active_support/logger"
7
+ require "active_support/tagged_logging"
8
+
9
+ module MimeActor
10
+ module Logging
11
+ extend ActiveSupport::Concern
12
+
13
+ include ActiveSupport::Configurable
14
+
15
+ included do
16
+ config_accessor :logger, default: ActiveSupport::TaggedLogging.new(ActiveSupport::Logger.new($stdout))
17
+ end
18
+
19
+ private
20
+
21
+ def fill_run_sheet(*scenes, &)
22
+ return yield unless logger.respond_to?(:tagged)
23
+
24
+ scenes.unshift "MimeActor" unless logger.formatter.current_tags.include?("MimeActor")
25
+
26
+ logger.tagged(*scenes, &)
27
+ end
28
+ end
29
+ end
@@ -1,55 +1,56 @@
1
1
  # frozen_string_literal: true
2
2
 
3
+ require "mime_actor/stage"
4
+ require "mime_actor/validator"
5
+
3
6
  require "active_support/concern"
4
7
  require "active_support/core_ext/array/wrap"
5
8
  require "active_support/core_ext/module/attribute_accessors"
6
- require "active_support/core_ext/object/blank"
7
9
  require "active_support/core_ext/string/inflections"
8
- require "action_dispatch/http/mime_type"
9
10
 
10
11
  module MimeActor
11
12
  module Rescue
12
13
  extend ActiveSupport::Concern
13
14
 
15
+ include Stage
16
+ include Validator
17
+
14
18
  included do
15
19
  mattr_accessor :actor_rescuers, instance_writer: false, default: []
16
20
  end
17
21
 
18
22
  module ClassMethods
19
23
  def rescue_actor_from(*klazzes, action: nil, format: nil, with: nil, &block)
20
- raise ArgumentError, "Error filter can't be empty" if klazzes.empty?
21
- raise ArgumentError, "Provide only the with: keyword argument or a block" if with.present? && block_given?
22
- raise ArgumentError, "Provide the with: keyword argument or a block" unless with.present? || block_given?
24
+ raise ArgumentError, "error filter is required" if klazzes.empty?
23
25
 
26
+ validate!(:with, with, block)
24
27
  with = block if block_given?
25
- raise ArgumentError, "Rescue handler can only be Symbol/Proc" unless with.is_a?(Proc) || with.is_a?(Symbol)
26
28
 
27
- if format.present?
28
- case format
29
- when Symbol
30
- raise ArgumentError, "Unsupported format: #{format}" unless Mime::SET.symbols.include?(format.to_sym)
31
- when Enumerable
32
- unfiltered = format.to_set
33
- filtered = unfiltered & Mime::SET.symbols.to_set
34
- rejected = unfiltered - filtered
35
- raise ArgumentError, "Unsupported formats: #{rejected.join(", ")}" if rejected.size.positive?
29
+ if action.present?
30
+ if action.is_a?(Enumerable)
31
+ validate!(:actions, action)
36
32
  else
37
- raise ArgumentError, "Format filter can only be Symbol/Enumerable"
33
+ validate!(:action, action)
38
34
  end
39
35
  end
40
36
 
41
- if action.present? && !(action.is_a?(Symbol) || action.is_a?(Enumerable))
42
- raise ArgumentError, "Action filter can only be Symbol/Enumerable"
37
+ if format.present?
38
+ if format.is_a?(Enumerable)
39
+ validate!(:formats, format)
40
+ else
41
+ validate!(:format, format)
42
+ end
43
43
  end
44
44
 
45
45
  klazzes.each do |klazz|
46
- error = if klazz.is_a?(Module)
46
+ error = case klazz
47
+ when Module
47
48
  klazz.name
48
- elsif klazz.is_a?(String)
49
+ when String
49
50
  klazz
50
51
  else
51
- raise ArgumentError,
52
- "#{klazz.inspect} must be a class/module or a String referencing a class/module"
52
+ message = "#{klazz.inspect} must be a Class/Module or a String referencing a Class/Module"
53
+ raise ArgumentError, message
53
54
  end
54
55
 
55
56
  # append at the end because strategies are read in reverse.
@@ -58,12 +59,13 @@ module MimeActor
58
59
  end
59
60
 
60
61
  def rescue_actor(error, action: nil, format: nil, context: self, visited: [])
61
- visited << error
62
+ return if visited.include?(error)
62
63
 
64
+ visited << error
63
65
  if (rescuer = dispatch_rescuer(error, format:, action:, context:))
64
66
  rescuer.call(error, format, action)
65
67
  error
66
- elsif error&.cause && !visited.include?(error.cause)
68
+ elsif error&.cause
67
69
  rescue_actor(error.cause, format:, action:, context:, visited:)
68
70
  end
69
71
  end
@@ -1,51 +1,54 @@
1
1
  # frozen_string_literal: true
2
2
 
3
- require "mime_actor/stage"
3
+ require "mime_actor/errors"
4
+ require "mime_actor/validator"
4
5
 
5
6
  require "active_support/concern"
6
7
  require "active_support/core_ext/array/extract_options"
7
8
  require "active_support/core_ext/array/wrap"
8
- require "active_support/core_ext/hash/indifferent_access"
9
9
  require "active_support/core_ext/module/attribute_accessors"
10
- require "action_dispatch/http/mime_type"
11
10
 
12
11
  module MimeActor
13
12
  module Scene
14
13
  extend ActiveSupport::Concern
15
14
 
16
- include Stage
15
+ include Validator
17
16
 
18
17
  included do
19
- mattr_accessor :acting_scenes, instance_writer: false, default: ActiveSupport::HashWithIndifferentAccess.new
18
+ mattr_accessor :acting_scenes, instance_writer: false, default: {}
20
19
  end
21
20
 
22
21
  module ClassMethods
23
22
  def compose_scene(*options)
24
23
  config = options.extract_options!
25
- actions = Array.wrap(config[:on])
26
-
27
- raise ArgumentError, "Action name can't be empty, e.g. on: :create" if actions.empty?
28
-
29
- options.each do |mime_type|
30
- raise ArgumentError, "Unsupported format: #{mime_type}" unless Mime::SET.symbols.include?(mime_type.to_sym)
24
+ validate!(:formats, options)
25
+
26
+ actions = config[:on]
27
+ if !actions
28
+ raise ArgumentError, "action is required"
29
+ elsif actions.is_a?(Enumerable)
30
+ validate!(:actions, actions)
31
+ else
32
+ validate!(:action, actions)
33
+ end
31
34
 
32
- actions.each do |action|
33
- if !acting_scenes.key?(action) && actor?(action)
34
- raise ArgumentError, "Action method already defined: #{action}"
35
- end
35
+ options.each do |format|
36
+ Array.wrap(actions).each do |action|
37
+ action_defined = (instance_methods + private_instance_methods).include?(action.to_sym)
38
+ raise MimeActor::ActionExisted, action if !acting_scenes.key?(action) && action_defined
36
39
 
37
40
  acting_scenes[action] ||= Set.new
38
- acting_scenes[action] |= [mime_type.to_sym]
41
+ acting_scenes[action] |= [format]
39
42
 
40
- next if actor?(action)
43
+ next if action_defined
41
44
 
42
45
  class_eval(
43
46
  # def index
44
- # self.cue_actor(:play_scene, :index)
47
+ # self.respond_to?(:start_scene) && self.start_scene(:index)
45
48
  # end
46
49
  <<-RUBY, __FILE__, __LINE__ + 1
47
50
  def #{action}
48
- self.cue_actor(:play_scene, :#{action})
51
+ self.respond_to?(:start_scene) && self.start_scene(:#{action})
49
52
  end
50
53
  RUBY
51
54
  )
@@ -1,18 +1,16 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  require "mime_actor/errors"
4
+ require "mime_actor/logging"
4
5
 
5
6
  require "active_support/concern"
6
- require "active_support/configurable"
7
7
  require "active_support/core_ext/module/attribute_accessors"
8
- require "abstract_controller/logger"
9
8
 
10
9
  module MimeActor
11
10
  module Stage
12
11
  extend ActiveSupport::Concern
13
12
 
14
- include ActiveSupport::Configurable
15
- include AbstractController::Logger
13
+ include Logging
16
14
 
17
15
  included do
18
16
  mattr_accessor :raise_on_missing_actor, instance_writer: false, default: false
@@ -20,29 +18,31 @@ module MimeActor
20
18
 
21
19
  module ClassMethods
22
20
  def actor?(actor_name)
23
- return action_methods.include?(actor_name.to_s) if singleton_methods.include?(:action_methods)
24
-
25
- instance_methods.include?(actor_name.to_sym)
21
+ # exclude public methods from ancestors
22
+ found = public_instance_methods(false).include?(actor_name.to_sym)
23
+ # exclude private methods from ancestors
24
+ if !found && private_instance_methods(false).include?(actor_name.to_sym)
25
+ logger.debug { "actor must be public method, #{actor_name} is private method" }
26
+ end
27
+ found
26
28
  end
27
- end
28
-
29
- def actor?(actor_name)
30
- return action_methods.include?(actor_name.to_s) if respond_to?(:action_methods)
31
-
32
- methods.include?(actor_name.to_sym)
33
- end
34
29
 
35
- def find_actor(actor_name)
36
- return method(actor_name) if actor?(actor_name)
37
-
38
- error = MimeActor::ActorNotFound.new(actor_name)
39
- raise error if raise_on_missing_actor
40
-
41
- logger.warn { "Actor not found: #{error.inspect}" }
30
+ def dispatch_cue(action: nil, format: nil, context: self, &block)
31
+ lambda do
32
+ context.instance_exec(&block)
33
+ rescue StandardError => e
34
+ (respond_to?(:rescue_actor) && rescue_actor(e, action:, format:, context:)) || raise
35
+ end
36
+ end
42
37
  end
43
38
 
44
39
  def cue_actor(actor_name, *args)
45
- return unless actor?(actor_name)
40
+ unless self.class.actor?(actor_name)
41
+ raise MimeActor::ActorNotFound, actor_name if raise_on_missing_actor
42
+
43
+ logger.warn { "actor not found, got: #{actor_name}" }
44
+ return
45
+ end
46
46
 
47
47
  result = public_send(actor_name, *args)
48
48
  if block_given?
@@ -0,0 +1,62 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "active_support/concern"
4
+ require "set" # required by mime_type with ruby <= 3.1
5
+ require "action_dispatch/http/mime_type"
6
+
7
+ module MimeActor
8
+ module Validator
9
+ extend ActiveSupport::Concern
10
+
11
+ included do
12
+ mattr_accessor :scene_formats, instance_writer: false, default: Mime::SET.symbols.to_set
13
+ end
14
+
15
+ module ClassMethods
16
+ def validate!(rule, *args)
17
+ validator = "validate_#{rule}"
18
+ raise NameError, "Validator not found, got: #{validator}" unless respond_to?(validator, true)
19
+
20
+ error = send(validator, *args)
21
+ raise error if error
22
+ end
23
+
24
+ def validate_action(unchecked)
25
+ ArgumentError.new("action must be a Symbol") unless unchecked.is_a?(Symbol)
26
+ end
27
+
28
+ def validate_actions(unchecked)
29
+ rejected = unchecked.reject { |action| action.is_a?(Symbol) }
30
+ NameError.new("invalid actions, got: #{rejected.join(", ")}") if rejected.size.positive?
31
+ end
32
+
33
+ def validate_format(unchecked)
34
+ return ArgumentError.new("format must be a Symbol") unless unchecked.is_a?(Symbol)
35
+
36
+ NameError.new("invalid format, got: #{unchecked}") unless scene_formats.include?(unchecked)
37
+ end
38
+
39
+ def validate_formats(unchecked)
40
+ unfiltered = unchecked.to_set
41
+ filtered = unfiltered & scene_formats
42
+ rejected = unfiltered - filtered
43
+
44
+ NameError.new("invalid formats, got: #{rejected.join(", ")}") if rejected.size.positive?
45
+ end
46
+
47
+ def validate_with(unchecked, block)
48
+ if unchecked.present? && block.present?
49
+ return ArgumentError.new("provide either the with: keyword argument or a block")
50
+ end
51
+ unless unchecked.present? || block.present?
52
+ return ArgumentError.new("provide the with: keyword argument or a block")
53
+ end
54
+
55
+ return if block.present?
56
+ return if unchecked.is_a?(Proc) || unchecked.is_a?(Symbol)
57
+
58
+ ArgumentError.new("with handler must be a Symbol or Proc, got: #{unchecked.inspect}")
59
+ end
60
+ end
61
+ end
62
+ end
@@ -11,8 +11,8 @@ module MimeActor
11
11
 
12
12
  module VERSION
13
13
  MAJOR = 0
14
- MINOR = 3
15
- BUILD = 2
14
+ MINOR = 4
15
+ BUILD = 0
16
16
  PRE = nil
17
17
 
18
18
  STRING = [MAJOR, MINOR, BUILD, PRE].compact.join(".")
data/lib/mime_actor.rb CHANGED
@@ -12,4 +12,6 @@ module MimeActor
12
12
  autoload :Scene
13
13
  autoload :Stage
14
14
  autoload :Rescue
15
+ autoload :Validator
16
+ autoload :Logging
15
17
  end
metadata CHANGED
@@ -1,14 +1,14 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: mime_actor
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.3.2
4
+ version: 0.4.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - Ryan Chang
8
8
  autorequire:
9
9
  bindir: exe
10
10
  cert_chain: []
11
- date: 2024-06-24 00:00:00.000000000 Z
11
+ date: 2024-06-27 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: actionpack
@@ -68,9 +68,11 @@ files:
68
68
  - lib/mime_actor.rb
69
69
  - lib/mime_actor/act.rb
70
70
  - lib/mime_actor/errors.rb
71
+ - lib/mime_actor/logging.rb
71
72
  - lib/mime_actor/rescue.rb
72
73
  - lib/mime_actor/scene.rb
73
74
  - lib/mime_actor/stage.rb
75
+ - lib/mime_actor/validator.rb
74
76
  - lib/mime_actor/version.rb
75
77
  - sig/mime_actor.rbs
76
78
  homepage: https://github.com/ryancyq/mime_actor