mime_actor 0.3.3 → 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: 35c75654432a3dd52002da0a92b31560432709c940143b6da12d6c8381298cd5
4
- data.tar.gz: 7a412d03ba99cc45ea2aceb4c3741a49b2bcbc53b04f2a627267e415c4e0955a
3
+ metadata.gz: 7f099315434127b3b7044aad85eca2f69664521753dd339af0f8b6829e0ef8dc
4
+ data.tar.gz: f8d79ab0813b5f3d63a8a991c9a0f39f328cc67e991738a21030a7fcaee3eae6
5
5
  SHA512:
6
- metadata.gz: db971845e8bd7544fea9b7bc711c9e290948eaeaf52526b49e72cab85023d6b8087f1f131c4f5f6ed70591ad342bfab94a09e44f3a9f6d250df6bd3c7d30b72e
7
- data.tar.gz: 0014236f7f1720f88f6bbb7e807c0d34760786b5febc1037d3f871c0b0f2a67d976c4ccadf58d939bff563b5c214bc25ca2160669590050bf6e0ca14c975ae1e
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,56 +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 "set" # remove when ruby > 3,1
9
- require "action_dispatch/http/mime_type"
10
10
 
11
11
  module MimeActor
12
12
  module Rescue
13
13
  extend ActiveSupport::Concern
14
14
 
15
+ include Stage
16
+ include Validator
17
+
15
18
  included do
16
19
  mattr_accessor :actor_rescuers, instance_writer: false, default: []
17
20
  end
18
21
 
19
22
  module ClassMethods
20
23
  def rescue_actor_from(*klazzes, action: nil, format: nil, with: nil, &block)
21
- raise ArgumentError, "Error filter can't be empty" if klazzes.empty?
22
- raise ArgumentError, "Provide only the with: keyword argument or a block" if with.present? && block_given?
23
- 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?
24
25
 
26
+ validate!(:with, with, block)
25
27
  with = block if block_given?
26
- raise ArgumentError, "Rescue handler can only be Symbol/Proc" unless with.is_a?(Proc) || with.is_a?(Symbol)
27
28
 
28
- if format.present?
29
- case format
30
- when Symbol
31
- raise ArgumentError, "Unsupported format: #{format}" unless Mime::SET.symbols.include?(format.to_sym)
32
- when Enumerable
33
- unfiltered = format.to_set
34
- filtered = unfiltered & Mime::SET.symbols.to_set
35
- rejected = unfiltered - filtered
36
- raise ArgumentError, "Unsupported formats: #{rejected.join(", ")}" if rejected.size.positive?
29
+ if action.present?
30
+ if action.is_a?(Enumerable)
31
+ validate!(:actions, action)
37
32
  else
38
- raise ArgumentError, "Format filter can only be Symbol/Enumerable"
33
+ validate!(:action, action)
39
34
  end
40
35
  end
41
36
 
42
- if action.present? && !(action.is_a?(Symbol) || action.is_a?(Enumerable))
43
- 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
44
43
  end
45
44
 
46
45
  klazzes.each do |klazz|
47
- error = if klazz.is_a?(Module)
46
+ error = case klazz
47
+ when Module
48
48
  klazz.name
49
- elsif klazz.is_a?(String)
49
+ when String
50
50
  klazz
51
51
  else
52
- raise ArgumentError,
53
- "#{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
54
54
  end
55
55
 
56
56
  # append at the end because strategies are read in reverse.
@@ -59,12 +59,13 @@ module MimeActor
59
59
  end
60
60
 
61
61
  def rescue_actor(error, action: nil, format: nil, context: self, visited: [])
62
- visited << error
62
+ return if visited.include?(error)
63
63
 
64
+ visited << error
64
65
  if (rescuer = dispatch_rescuer(error, format:, action:, context:))
65
66
  rescuer.call(error, format, action)
66
67
  error
67
- elsif error&.cause && !visited.include?(error.cause)
68
+ elsif error&.cause
68
69
  rescue_actor(error.cause, format:, action:, context:, visited:)
69
70
  end
70
71
  end
@@ -1,52 +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 "set" # remove when ruby > 3,1
11
- require "action_dispatch/http/mime_type"
12
10
 
13
11
  module MimeActor
14
12
  module Scene
15
13
  extend ActiveSupport::Concern
16
14
 
17
- include Stage
15
+ include Validator
18
16
 
19
17
  included do
20
- mattr_accessor :acting_scenes, instance_writer: false, default: ActiveSupport::HashWithIndifferentAccess.new
18
+ mattr_accessor :acting_scenes, instance_writer: false, default: {}
21
19
  end
22
20
 
23
21
  module ClassMethods
24
22
  def compose_scene(*options)
25
23
  config = options.extract_options!
26
- actions = Array.wrap(config[:on])
27
-
28
- raise ArgumentError, "Action name can't be empty, e.g. on: :create" if actions.empty?
29
-
30
- options.each do |mime_type|
31
- 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
32
34
 
33
- actions.each do |action|
34
- if !acting_scenes.key?(action) && actor?(action)
35
- raise ArgumentError, "Action method already defined: #{action}"
36
- 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
37
39
 
38
40
  acting_scenes[action] ||= Set.new
39
- acting_scenes[action] |= [mime_type.to_sym]
41
+ acting_scenes[action] |= [format]
40
42
 
41
- next if actor?(action)
43
+ next if action_defined
42
44
 
43
45
  class_eval(
44
46
  # def index
45
- # self.cue_actor(:play_scene, :index)
47
+ # self.respond_to?(:start_scene) && self.start_scene(:index)
46
48
  # end
47
49
  <<-RUBY, __FILE__, __LINE__ + 1
48
50
  def #{action}
49
- self.cue_actor(:play_scene, :#{action})
51
+ self.respond_to?(:start_scene) && self.start_scene(:#{action})
50
52
  end
51
53
  RUBY
52
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 = 3
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.3
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