auxiliary_rails 0.3.1 → 0.4.1

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.
Files changed (38) hide show
  1. checksums.yaml +4 -4
  2. data/.github/workflows/main.yml +32 -0
  3. data/.github/workflows/publish-gpr.yml +32 -0
  4. data/.github/workflows/publish-rubygems.yml +31 -0
  5. data/.rubocop.yml +10 -6
  6. data/.rubocop_todo.yml +1 -7
  7. data/.ruby-version +1 -1
  8. data/CHANGELOG.md +11 -1
  9. data/CODE_OF_CONDUCT.md +128 -0
  10. data/Gemfile +9 -1
  11. data/Gemfile.lock +175 -138
  12. data/README.md +77 -17
  13. data/auxiliary_rails.gemspec +12 -15
  14. data/bin/rspec +29 -0
  15. data/lib/auxiliary_rails/application/command.rb +3 -6
  16. data/lib/auxiliary_rails/application/error.rb +44 -4
  17. data/lib/auxiliary_rails/application/form.rb +1 -0
  18. data/lib/auxiliary_rails/application/query.rb +11 -7
  19. data/lib/auxiliary_rails/application/service.rb +42 -0
  20. data/lib/auxiliary_rails/concerns/callable.rb +23 -0
  21. data/lib/auxiliary_rails/concerns/errorable.rb +12 -12
  22. data/lib/auxiliary_rails/concerns/performable.rb +14 -22
  23. data/lib/auxiliary_rails/railtie.rb +2 -1
  24. data/lib/auxiliary_rails/version.rb +1 -1
  25. data/lib/auxiliary_rails/view_helpers/display_helper.rb +30 -0
  26. data/lib/auxiliary_rails/view_helpers.rb +4 -0
  27. data/lib/auxiliary_rails.rb +3 -1
  28. data/lib/generators/auxiliary_rails/install_errors_controller_generator.rb +31 -0
  29. data/lib/generators/auxiliary_rails/install_generator.rb +1 -0
  30. data/lib/generators/auxiliary_rails/service_generator.rb +48 -0
  31. data/lib/generators/auxiliary_rails/templates/commands/command_spec_template.rb +1 -1
  32. data/lib/generators/auxiliary_rails/templates/errors_controller/errors_controller_template.rb +15 -0
  33. data/lib/generators/auxiliary_rails/templates/errors_controller/not_found_template.html.erb +2 -0
  34. data/lib/generators/auxiliary_rails/templates/errors_controller/show_template.html.erb +1 -0
  35. data/lib/generators/auxiliary_rails/templates/errors_controller/unacceptable_template.html.erb +2 -0
  36. data/lib/generators/auxiliary_rails/templates/services/service_spec_template.rb +5 -0
  37. data/lib/generators/auxiliary_rails/templates/services/service_template.rb +10 -0
  38. metadata +24 -111
@@ -1,10 +1,50 @@
1
1
  module AuxiliaryRails
2
2
  module Application
3
- class Error < StandardError
4
- # @return [self] Creates new error object
5
- def self.wrap(error)
6
- new(error.message)
3
+ # @abstract
4
+ class Error < RuntimeError
5
+ attr_accessor :context
6
+ attr_reader :exc, :severity
7
+
8
+ class << self
9
+ def i18n_scope
10
+ "errors.#{name.underscore}"
11
+ end
12
+
13
+ # @return [self] Wraps exception into a new ApplicationError object
14
+ def wrap(exc, context: {}, severity: nil)
15
+ new(exc.message, context: context, exc: exc, severity: severity)
16
+ end
7
17
  end
18
+
19
+ def initialize(message = nil, context: {}, exc: nil, severity: nil)
20
+ super(message)
21
+
22
+ self.context = default_context.merge(context || {})
23
+ self.exc = exc
24
+ self.severity = severity&.to_sym || default_severity
25
+ end
26
+
27
+ def default_context
28
+ {}
29
+ end
30
+
31
+ def default_severity
32
+ :error
33
+ end
34
+
35
+ def friendly_message
36
+ I18n.t(:default,
37
+ scope: self.class.i18n_scope,
38
+ default: 'We are sorry, but something went wrong.')
39
+ end
40
+
41
+ def report
42
+ raise NotImplementedError
43
+ end
44
+
45
+ protected
46
+
47
+ attr_writer :exc, :severity
8
48
  end
9
49
  end
10
50
  end
@@ -1,4 +1,5 @@
1
1
  require 'active_model'
2
+ require 'auxiliary_rails/concerns/performable'
2
3
 
3
4
  module AuxiliaryRails
4
5
  module Application
@@ -1,30 +1,34 @@
1
+ require 'auxiliary_rails/concerns/callable'
1
2
  require 'auxiliary_rails/concerns/errorable'
2
3
  require 'dry/core/class_attributes'
3
4
  require 'dry-initializer'
4
5
 
5
6
  module AuxiliaryRails
6
7
  module Application
8
+ # @abstract
7
9
  class Query
8
10
  extend Dry::Core::ClassAttributes
9
11
  extend Dry::Initializer
12
+ include AuxiliaryRails::Concerns::Callable
10
13
  include AuxiliaryRails::Concerns::Errorable
11
14
 
12
15
  defines :default_relation
13
16
 
14
- option :relation, default: proc { nil }
15
-
16
- def self.call(*args)
17
- new(*args).call
18
- end
17
+ option :relation, default: proc {}
19
18
 
20
19
  def call
21
20
  ensure_proper_relation_types!
22
21
 
23
22
  perform
24
23
 
24
+ if !queriable_object?(query)
25
+ error!('Invalid class of resulted query')
26
+ end
27
+
25
28
  query
26
29
  end
27
30
 
31
+ # @abstract
28
32
  def perform
29
33
  raise NotImplementedError
30
34
  end
@@ -41,7 +45,7 @@ module AuxiliaryRails
41
45
 
42
46
  private
43
47
 
44
- # rubocop:disable Metrics/AbcSize, Style/GuardClause
48
+ # rubocop:disable Style/GuardClause
45
49
  def ensure_proper_relation_types!
46
50
  if self.class.default_relation.nil?
47
51
  error!('Undefined `default_relation`')
@@ -53,7 +57,7 @@ module AuxiliaryRails
53
57
  error!('Invalid class of `relation` option')
54
58
  end
55
59
  end
56
- # rubocop:enable Metrics/AbcSize, Style/GuardClause
60
+ # rubocop:enable Style/GuardClause
57
61
 
58
62
  def queriable_object?(object)
59
63
  object.is_a?(ActiveRecord::Relation)
@@ -0,0 +1,42 @@
1
+ require 'active_support/concern'
2
+ require 'active_support/ordered_options'
3
+
4
+ module AuxiliaryRails
5
+ module Application
6
+ module Service
7
+ def self.included(klass)
8
+ raise AuxiliaryRails::Error,
9
+ "Use `extend` insted of `include` for #{self} in #{klass}"
10
+ end
11
+
12
+ # rubocop:disable Metrics/AbcSize, Metrics/MethodLength
13
+ def config
14
+ return @config if @config.present?
15
+
16
+ # load from constant
17
+ if const_defined?(:CONFIG)
18
+ return @config = ActiveSupport::OrderedOptions.new.update(const_get(:CONFIG))
19
+ end
20
+
21
+ service_name = name.underscore
22
+
23
+ # load from service config file
24
+ if Rails.root.join("config/services/#{service_name}.yml").exist?
25
+ return @config = Rails.application.config_for("services/#{service_name}")
26
+ end
27
+
28
+ # load from application config
29
+ if Object.const_defined?(:Config) && Config.respond_to?(:const_name)
30
+ app_config = Object.const_get(Config.const_name)
31
+ if app_config.dig(:services, service_name).present?
32
+ return @config = app_config[:services][service_name]
33
+ end
34
+ end
35
+
36
+ raise AuxiliaryRails::Error,
37
+ "Unable to find suitable config for #{name} module"
38
+ end
39
+ # rubocop:enable Metrics/AbcSize, Metrics/MethodLength
40
+ end
41
+ end
42
+ end
@@ -0,0 +1,23 @@
1
+ module AuxiliaryRails
2
+ module Concerns
3
+ module Callable
4
+ def self.included(klass)
5
+ klass.extend(ClassMethods)
6
+ end
7
+
8
+ module ClassMethods
9
+ # Initializes object and runs <tt>#call</tt> method.
10
+ #
11
+ # @return [self]
12
+ def call(*args, **kws)
13
+ new(*args, **kws).call
14
+ end
15
+ end
16
+
17
+ # @abstract
18
+ def call
19
+ raise NotImplementedError
20
+ end
21
+ end
22
+ end
23
+ end
@@ -1,22 +1,22 @@
1
1
  module AuxiliaryRails
2
2
  module Concerns
3
3
  module Errorable
4
- # extend ActiveSupport::Concern
4
+ extend ActiveSupport::Concern
5
5
 
6
- def error!(message = nil)
7
- message ||= "`#{self.class}` raised error."
8
- raise error_class, message
9
- end
10
-
11
- if defined?(ApplicationError)
12
- def error_class
13
- ApplicationError
14
- end
15
- else
6
+ class_methods do
16
7
  def error_class
17
- AuxiliaryRails::Application::Error
8
+ if Object.const_defined?(:ApplicationError)
9
+ ApplicationError
10
+ else
11
+ AuxiliaryRails::Application::Error
12
+ end
18
13
  end
19
14
  end
15
+
16
+ def error!(message = nil)
17
+ message ||= "`#{self.class}` raised error."
18
+ raise self.class.error_class, message
19
+ end
20
20
  end
21
21
  end
22
22
  end
@@ -1,4 +1,5 @@
1
1
  require 'active_support/concern'
2
+ require 'auxiliary_rails/concerns/callable'
2
3
  require 'auxiliary_rails/concerns/errorable'
3
4
 
4
5
  module AuxiliaryRails
@@ -6,14 +7,9 @@ module AuxiliaryRails
6
7
  module Performable
7
8
  extend ActiveSupport::Concern
8
9
  include ActiveModel::Validations
10
+ include AuxiliaryRails::Concerns::Callable
9
11
  include AuxiliaryRails::Concerns::Errorable
10
12
 
11
- class_methods do
12
- def call(*args)
13
- new(*args).call
14
- end
15
- end
16
-
17
13
  def call(options = {})
18
14
  ensure_empty_status!
19
15
 
@@ -58,24 +54,15 @@ module AuxiliaryRails
58
54
  # Provides ability to execude block of the code depending on
59
55
  # command execution status
60
56
  #
61
- # @param status [Symol] Desired command status
62
- # @param &_block Code to be executed if status matches
57
+ # @param status [Symbol] Desired command status
58
+ # @param &block Code to be executed if status matches
63
59
  # @return [self]
64
- def on(status, &_block)
65
- ensure_execution!
66
-
67
- return self unless status?(status)
68
-
69
- yield(self) if block_given?
60
+ def on(status, &block)
61
+ yield(self) if status?(status) && block
70
62
 
71
63
  self
72
64
  end
73
65
 
74
- # Shortcut for <tt>ActiveRecord::Base.transaction</tt>
75
- def transaction(&block)
76
- ActiveRecord::Base.transaction(&block) if block_given?
77
- end
78
-
79
66
  protected
80
67
 
81
68
  attr_accessor :performable_status
@@ -86,19 +73,19 @@ module AuxiliaryRails
86
73
  def ensure_empty_errors!
87
74
  return if errors.empty?
88
75
 
89
- error!("`#{self.class}` contains errors.")
76
+ error!("'#{self.class}' contains errors.")
90
77
  end
91
78
 
92
79
  def ensure_empty_status!
93
80
  return if performable_status.nil?
94
81
 
95
- error!("`#{self.class}` was already executed.")
82
+ error!("'#{self.class}' was already executed.")
96
83
  end
97
84
 
98
85
  def ensure_execution!
99
86
  return if performable_status.present?
100
87
 
101
- error!("`#{self.class}` was not executed yet.")
88
+ error!("'#{self.class}' was not executed yet.")
102
89
  end
103
90
 
104
91
  # Sets command's status to <tt>failure</tt>.
@@ -123,6 +110,11 @@ module AuxiliaryRails
123
110
  self.performable_status = :success
124
111
  self
125
112
  end
113
+
114
+ # Shortcut for <tt>ActiveRecord::Base.transaction</tt>
115
+ def transaction(&block)
116
+ ActiveRecord::Base.transaction(&block) if block
117
+ end
126
118
  end
127
119
  end
128
120
  end
@@ -1,9 +1,10 @@
1
+ require 'rails/railtie'
1
2
  require 'auxiliary_rails/view_helpers'
2
3
 
3
4
  module AuxiliaryRails
4
5
  class Railtie < Rails::Railtie
5
6
  initializer 'auxiliary_rails.view_helpers' do
6
- ActionView::Base.send :include, ViewHelpers
7
+ ActionView::Base.include ViewHelpers
7
8
  end
8
9
  end
9
10
  end
@@ -1,3 +1,3 @@
1
1
  module AuxiliaryRails
2
- VERSION = '0.3.1'.freeze
2
+ VERSION = '0.4.1'.freeze
3
3
  end
@@ -0,0 +1,30 @@
1
+ module AuxiliaryRails
2
+ module ViewHelpers
3
+ module DisplayHelper
4
+ DISPLAY_NAME_METHODS = %i[
5
+ display_name
6
+ name
7
+ title
8
+ full_name
9
+ username
10
+ email
11
+ ].freeze
12
+
13
+ def display_name(resource)
14
+ return if resource.nil?
15
+
16
+ DISPLAY_NAME_METHODS.each do |method_name|
17
+ if resource.respond_to?(method_name)
18
+ return resource.public_send(method_name)
19
+ end
20
+ end
21
+
22
+ if resource.respond_to?(:model_name) && resource.respond_to?(:id)
23
+ return "#{resource.model_name.human} ##{resource.id}"
24
+ end
25
+
26
+ resource.to_s
27
+ end
28
+ end
29
+ end
30
+ end
@@ -1,5 +1,9 @@
1
+ require 'auxiliary_rails/view_helpers/display_helper'
2
+
1
3
  module AuxiliaryRails
2
4
  module ViewHelpers
5
+ include DisplayHelper
6
+
3
7
  def current_controller?(*ctrl_names)
4
8
  ctrl_names.include?(params[:controller])
5
9
  end
@@ -1,7 +1,9 @@
1
1
  require 'auxiliary_rails/application/command'
2
- require 'auxiliary_rails/application/form'
3
2
  require 'auxiliary_rails/application/error'
3
+ require 'auxiliary_rails/application/form'
4
+ require 'auxiliary_rails/application/service'
4
5
  require 'auxiliary_rails/application/query'
6
+
5
7
  require 'auxiliary_rails/railtie'
6
8
  require 'auxiliary_rails/version'
7
9
 
@@ -0,0 +1,31 @@
1
+ require 'rails'
2
+
3
+ module AuxiliaryRails
4
+ class InstallErrorsControllerGenerator < ::Rails::Generators::Base
5
+ source_root File.expand_path('templates/errors_controller', __dir__)
6
+
7
+ VIEW_TEMPLATES = %w[show not_found unacceptable].freeze
8
+
9
+ def copy_controller_file
10
+ copy_file 'errors_controller_template.rb',
11
+ 'app/controllers/errors_controller.rb'
12
+ end
13
+
14
+ def copy_view_files
15
+ VIEW_TEMPLATES.each do |tempate_name|
16
+ copy_file "#{tempate_name}_template.html.erb",
17
+ "app/views/errors/#{tempate_name}.html.erb"
18
+ end
19
+ end
20
+
21
+ def add_route
22
+ route "match '/404', to: 'errors#not_found', via: :all"
23
+ route "match '/422', to: 'errors#unacceptable', via: :all"
24
+ route "match '/500', to: 'errors#show', via: :all"
25
+ end
26
+
27
+ def add_exceptions_app_config
28
+ application 'config.exceptions_app = routes'
29
+ end
30
+ end
31
+ end
@@ -5,6 +5,7 @@ module AuxiliaryRails
5
5
  def install
6
6
  generate 'auxiliary_rails:install_commands'
7
7
  generate 'auxiliary_rails:install_errors'
8
+ generate 'auxiliary_rails:install_errors_controller'
8
9
  end
9
10
  end
10
11
  end
@@ -0,0 +1,48 @@
1
+ require 'rails'
2
+
3
+ module AuxiliaryRails
4
+ class ServiceGenerator < ::Rails::Generators::NamedBase
5
+ desc 'Stubs out a new Service and spec.'
6
+
7
+ source_root File.expand_path('templates/services', __dir__)
8
+
9
+ class_option :path,
10
+ type: :string,
11
+ default: 'app/services',
12
+ desc: 'Service location'
13
+
14
+ def create_service_dir
15
+ FileUtils.mkdir_p("#{service_file_path}/#{service_file_name}")
16
+ end
17
+
18
+ def create_service_file
19
+ FileUtils.mkdir_p(service_file_path)
20
+ template 'service_template.rb',
21
+ "#{service_file_path}/#{service_file_name}.rb"
22
+ end
23
+
24
+ def create_service_spec_file
25
+ FileUtils.mkdir_p(service_spec_path)
26
+ template 'service_spec_template.rb',
27
+ "#{service_spec_path}/#{service_file_name}_spec.rb"
28
+ end
29
+
30
+ private
31
+
32
+ def service_module_name
33
+ "#{class_name.gsub(/Service$/, '')}Service"
34
+ end
35
+
36
+ def service_file_name
37
+ service_module_name.underscore
38
+ end
39
+
40
+ def service_file_path
41
+ options[:path]
42
+ end
43
+
44
+ def service_spec_path
45
+ service_file_path.gsub(%r{^app/}, 'spec/')
46
+ end
47
+ end
48
+ end
@@ -1,6 +1,6 @@
1
1
  require 'rails_helper'
2
2
 
3
- describe <%= command_class_name %> do
3
+ RSpec.describe <%= command_class_name %> do
4
4
  describe '#call' do
5
5
  it 'succeeds' do
6
6
  # TODO: implement spec for command
@@ -0,0 +1,15 @@
1
+ class ErrorsController < ApplicationController
2
+ DEFAULT_ERROR_STATUS = 500
3
+
4
+ def show
5
+ render status: params[:status] || DEFAULT_ERROR_STATUS
6
+ end
7
+
8
+ def not_found
9
+ render status: :not_found
10
+ end
11
+
12
+ def unacceptable
13
+ render status: :unprocessable_entity
14
+ end
15
+ end
@@ -0,0 +1,2 @@
1
+ <h1>The page you were looking for doesn't exist.</h1>
2
+ <p>You may have mistyped the address or the page may have moved.</p>
@@ -0,0 +1 @@
1
+ <h1>We're sorry, but something went wrong.</h1>
@@ -0,0 +1,2 @@
1
+ <h1>The change you wanted was rejected.</h1>
2
+ <p>Maybe you tried to change something you didn't have access to.</p>
@@ -0,0 +1,5 @@
1
+ require 'rails_helper'
2
+
3
+ RSpec.describe <%= service_module_name %> do
4
+ pending "add some examples to (or delete) #{__FILE__}"
5
+ end
@@ -0,0 +1,10 @@
1
+ module <%= service_module_name %>
2
+ extend AuxiliaryRails::Application::Service
3
+
4
+ module_function
5
+
6
+ CONFIG = {
7
+ key: 'value'
8
+ }.freeze
9
+
10
+ end