modulorails 0.2.2 → 1.0.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 (67) hide show
  1. checksums.yaml +4 -4
  2. data/.dockerignore +14 -0
  3. data/.gitignore +3 -0
  4. data/.rubocop.yml +62 -0
  5. data/.travis.yml +23 -4
  6. data/Appraisals +18 -0
  7. data/CHANGELOG.md +42 -1
  8. data/Dockerfile.ruby25 +34 -0
  9. data/Dockerfile.ruby26 +28 -0
  10. data/Dockerfile.ruby27 +25 -0
  11. data/Dockerfile.ruby30 +25 -0
  12. data/Dockerfile.ruby31 +25 -0
  13. data/Gemfile.lock +59 -54
  14. data/README.md +16 -4
  15. data/Rakefile +1 -1
  16. data/app/assets/stylesheets/modulorails.css +21 -0
  17. data/app/helpers/modulorails/application_helper.rb +8 -0
  18. data/config/locales/en.yml +3 -0
  19. data/docker-compose.debug.yml +47 -0
  20. data/docker-compose.yml +37 -0
  21. data/entrypoints/appraisal_test.sh +7 -0
  22. data/gemfiles/rails_52.gemfile +9 -0
  23. data/gemfiles/rails_60.gemfile +9 -0
  24. data/gemfiles/rails_61.gemfile +9 -0
  25. data/gemfiles/rails_70.gemfile +9 -0
  26. data/lib/generators/modulorails/docker/docker_generator.rb +19 -0
  27. data/lib/generators/modulorails/docker/templates/Dockerfile.prod.tt +57 -0
  28. data/lib/generators/modulorails/docker/templates/Dockerfile.tt +26 -0
  29. data/lib/generators/modulorails/docker/templates/config/database.yml.tt +32 -0
  30. data/lib/generators/modulorails/docker/templates/docker-compose.prod.yml.tt +50 -0
  31. data/lib/generators/modulorails/docker/templates/docker-compose.yml.tt +71 -0
  32. data/lib/generators/modulorails/docker/templates/entrypoints/docker-entrypoint.sh.tt +20 -0
  33. data/lib/generators/modulorails/docker/templates/entrypoints/webpack-entrypoint.sh.tt +7 -0
  34. data/lib/generators/modulorails/gitlabci/gitlabci_generator.rb +34 -0
  35. data/lib/generators/modulorails/gitlabci/templates/.gitlab-ci.yml.tt +118 -0
  36. data/lib/generators/modulorails/gitlabci/templates/.modulorails-gitlab-ci +6 -0
  37. data/lib/generators/modulorails/gitlabci/templates/config/database-ci.yml.tt +8 -0
  38. data/lib/generators/modulorails/healthcheck/health_check_generator.rb +41 -0
  39. data/lib/generators/modulorails/healthcheck/templates/.modulorails-health_check +6 -0
  40. data/lib/generators/modulorails/healthcheck/templates/config/initializers/health_check.rb.tt +100 -0
  41. data/lib/generators/modulorails/rubocop/rubocop_generator.rb +24 -0
  42. data/lib/generators/modulorails/rubocop/templates/rubocop.yml.tt +18 -0
  43. data/lib/generators/modulorails/self_update/self_update_generator.rb +32 -0
  44. data/lib/generators/modulorails/service/service_generator.rb +13 -0
  45. data/lib/generators/modulorails/service/templates/service.rb.tt +28 -0
  46. data/lib/modulorails/configuration.rb +8 -2
  47. data/lib/modulorails/data.rb +21 -2
  48. data/lib/modulorails/error_data.rb +21 -0
  49. data/lib/modulorails/errors/base_error.rb +4 -0
  50. data/lib/modulorails/errors/errors.rb +3 -0
  51. data/lib/modulorails/errors/invalid_format_error.rb +14 -0
  52. data/lib/modulorails/errors/invalid_value_error.rb +14 -0
  53. data/lib/modulorails/railtie.rb +35 -3
  54. data/lib/modulorails/services/base_service.rb +203 -0
  55. data/lib/modulorails/services/logs_for_method_service.rb +42 -0
  56. data/lib/modulorails/services/services.rb +2 -0
  57. data/lib/modulorails/success_data.rb +17 -0
  58. data/lib/modulorails/validators/database_configuration.rb +9 -3
  59. data/lib/modulorails/version.rb +4 -1
  60. data/lib/modulorails.rb +46 -21
  61. data/modulorails.gemspec +4 -0
  62. metadata +114 -17
  63. data/lib/generators/gitlabci_generator.rb +0 -134
  64. data/lib/generators/templates/.gitlab-ci.yml +0 -72
  65. data/lib/generators/templates/.modulorails-gitlab-ci +0 -3
  66. data/lib/generators/templates/config/database-ci.yml +0 -7
  67. data/lib/modulorails/updater.rb +0 -46
@@ -0,0 +1,24 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'rails/generators'
4
+
5
+ class Modulorails::RubocopGenerator < Rails::Generators::Base
6
+ source_root File.expand_path('templates', __dir__)
7
+ desc 'This generator creates a configuration for Rubocop'
8
+
9
+ def create_config_files
10
+ rubocop_config_path = Rails.root.join('.rubocop.yml')
11
+ gitlab_config_path = Rails.root.join('.gitlab-ci.yml')
12
+
13
+ template "rubocop.yml", rubocop_config_path, force: true
14
+
15
+ unless File.read(gitlab_config_path).match?(/\s+extends:\s+.lint\s*$/)
16
+ append_file gitlab_config_path do
17
+ <<~YAML
18
+ rubocop:
19
+ extends: .lint
20
+ YAML
21
+ end
22
+ end
23
+ end
24
+ end
@@ -0,0 +1,18 @@
1
+ # The behavior of RuboCop can be controlled via the .rubocop.yml
2
+ # configuration file. It makes it possible to enable/disable
3
+ # certain cops (checks) and to alter their behavior if they accept
4
+ # any parameters. The file can be placed either in your home
5
+ # directory or in some project directory.
6
+ #
7
+ # RuboCop will start looking for the configuration file in the directory
8
+ # where the inspected file is and continue its way up to the root directory.
9
+ #
10
+ # See https://docs.rubocop.org/rubocop/configuration
11
+
12
+ inherit_gem:
13
+ modulorails: .rubocop.yml
14
+
15
+ # Take into account the exclude list from the gem
16
+ inherit_mode:
17
+ merge:
18
+ - Exclude
@@ -0,0 +1,32 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'rails/generators'
4
+
5
+ # Author: Matthieu 'ciappa_m' Ciappara
6
+ # This updates modulorails by editing the gemfile and running a bundle update
7
+ class Modulorails::SelfUpdateGenerator < Rails::Generators::Base
8
+ source_root File.expand_path('templates', __dir__)
9
+ desc 'This generator updates Modulorails if required'
10
+
11
+ LATEST_VERSION_URL = 'https://rubygems.org/api/v1/versions/modulorails/latest.json'.freeze
12
+
13
+ def create_config_file
14
+ modulorails_version = Gem::Version.new(Modulorails::VERSION)
15
+
16
+ # Get the last published version
17
+ last_published_version_s = HTTParty.get(LATEST_VERSION_URL).parsed_response['version']
18
+ last_published_version = Gem::Version.new(last_published_version_s)
19
+
20
+ # Do nothing if we could not fetch the last published version (whatever the reason)
21
+ # Or if the current version is the same as the last published version
22
+ return if last_published_version <= modulorails_version
23
+
24
+ # Add gem to Gemfile
25
+ gsub_file 'Gemfile', /^\s*gem\s['"]modulorails['"].*$/, "gem 'modulorails', '= #{last_published_version}'"
26
+
27
+ # Update the gem and the Gemfile.lock
28
+ system('bundle install')
29
+ rescue StandardError => e
30
+ $stderr.puts("[Modulorails] Error: cannot generate health_check configuration: #{e.message}")
31
+ end
32
+ end
@@ -0,0 +1,13 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'rails/generators'
4
+
5
+ class Modulorails::ServiceGenerator < Rails::Generators::NamedBase
6
+ source_root File.expand_path('templates', __dir__)
7
+ desc 'This generator creates a service inheriting Modulorails::BaseService'
8
+ argument :arguments, type: :array, default: [], banner: 'argument argument'
9
+
10
+ def create_service_files
11
+ template "service.rb", File.join("app/services", class_path, "#{file_name}_service.rb")
12
+ end
13
+ end
@@ -0,0 +1,28 @@
1
+ <% module_namespacing do -%>
2
+ # @author <INSERT YOUR NAME HERE>
3
+ # <DESCRIBE YOUR CLASS HERE>
4
+ <%- if arguments.size >= 1 -%>
5
+ #
6
+ <%- arguments.each do |argument| -%>
7
+ # @!attribute <%= argument %>
8
+ # @return <DESCRIBE YOUR ARGUMENT HERE>
9
+ <%- end -%>
10
+ <%- end -%>
11
+ class <%= class_name %>Service < ::ApplicationService
12
+ <%- if arguments.size >= 1 -%>
13
+
14
+ def initialize(<%= arguments.join(', ') %>)
15
+ super()
16
+
17
+ <%- arguments.each do |argument| -%>
18
+ <%= "@#{argument} = #{argument}" %>
19
+ <%- end -%>
20
+ end
21
+ <%- end -%>
22
+
23
+ def call
24
+ # TODO
25
+ end
26
+
27
+ end
28
+ <% end -%>
@@ -4,7 +4,7 @@ module Modulorails
4
4
  class Configuration
5
5
  # All the keys to configure the gem
6
6
  attr_accessor :_name, :_main_developer, :_project_manager, :_endpoint, :_api_key,
7
- :_no_auto_update
7
+ :_no_auto_update, :_production_url, :_staging_url, :_review_base_url
8
8
 
9
9
  # This allows to define a DSL to configure the gem
10
10
  # Example:
@@ -14,8 +14,14 @@ module Modulorails
14
14
  # config.project_manager 'pm@modulotech.fr'
15
15
  # config.endpoint "intranet's endpoint"
16
16
  # config.api_key "intranet's api key"
17
+ # config.production_url "production.app.com"
18
+ # config.staging_url "staging.app.com"
19
+ # config.review_base_url "review.app.com"
17
20
  # end
18
- %i[name main_developer project_manager endpoint api_key no_auto_update].each do |field|
21
+ %i[
22
+ name main_developer project_manager endpoint api_key no_auto_update production_url staging_url
23
+ review_base_url
24
+ ].each do |field|
19
25
  define_method(field) do |value=nil|
20
26
  # No value means we want to get the field
21
27
  return send("_#{field}") unless value
@@ -10,7 +10,8 @@ module Modulorails
10
10
  # All the data handled by this class
11
11
  ATTRIBUTE_KEYS = %i[
12
12
  name main_developer project_manager repository type rails_name ruby_version rails_version
13
- bundler_version modulorails_version adapter db_version adapter_version
13
+ bundler_version modulorails_version adapter db_version adapter_version production_url
14
+ staging_url review_base_url
14
15
  ].freeze
15
16
 
16
17
  # Useful if the gem's user need to read one of the data
@@ -30,7 +31,7 @@ module Modulorails
30
31
  # Get the gem's specifications to fetch the versions of critical gems
31
32
  loaded_specs = Gem.loaded_specs
32
33
 
33
- # The three data written by the user in the configuration
34
+ # The data written by the user in the configuration
34
35
  # The name is the usual name of the project, the one used in conversations at Modulotech
35
36
  @name = configuration.name
36
37
  # The main developer, the lead developer, in short the developer to call when something's
@@ -39,6 +40,19 @@ module Modulorails
39
40
  # The project manager of the application; the other person to call when something's wrong with
40
41
  # the application ;)
41
42
  @project_manager = configuration.project_manager
43
+ # The URL of the production environment for the application
44
+ @production_url = configuration.production_url
45
+ # The URL of the staging environment for the application
46
+ @staging_url = configuration.staging_url
47
+ # The base URL of the review environment for the application.
48
+ # A real review URL is built like this at Modulotech:
49
+ # https://review-#{shortened_branch_name}-#{ci_slug}.#{review_base_url}
50
+ # Example:
51
+ # review_base_url: dev.app.com
52
+ # branch_name: 786-a_super_branch => shortened_branch_name: 786-a_sup
53
+ # ci_slug: jzzham
54
+ # |-> https://review-786-a_sup-jzzham.dev.app.com/
55
+ @review_base_url = configuration.review_base_url
42
56
 
43
57
  # Theorically, origin is the main repository of the project and git is the sole VCS we use
44
58
  # at Modulotech
@@ -101,6 +115,11 @@ module Modulorails
101
115
  'adapter' => @adapter,
102
116
  'db_version' => @db_version,
103
117
  'gem_version' => @adapter_version
118
+ },
119
+ 'urls' => {
120
+ 'production' => @production_url,
121
+ 'staging' => @staging_url,
122
+ 'review_base' => @review_base_url
104
123
  }
105
124
  }
106
125
  }
@@ -0,0 +1,21 @@
1
+ # @author Matthieu Ciappara <ciappa_m@modulotech>
2
+ # An error encountered during an operation with additional data.
3
+ class Modulorails::ErrorData
4
+ # @!attribute r errors
5
+ # An error message or an array of error messages (those will be joined by a coma and a space).
6
+ # @!attribute r exception
7
+ # The exception that caused the error. Defaults to nil.
8
+ attr_reader :errors, :exception
9
+
10
+ # @param errors [String,Array<String>] An error message or an array of error messages
11
+ # @param exception [Exception,nil] The exception that caused the error.
12
+ def initialize(errors, exception: nil)
13
+ @errors = errors.respond_to?(:join) ? errors.join(', ') : errors.to_s
14
+ @exception = exception
15
+ end
16
+
17
+ # @return [false] An error always means the operation was not a success.
18
+ def success?
19
+ false
20
+ end
21
+ end
@@ -0,0 +1,4 @@
1
+ # @author Matthieu CIAPPARA <ciappa_m@modulotech.fr>
2
+ # The base class for modulorails exceptions.
3
+ class Modulorails::BaseError < RuntimeError
4
+ end
@@ -0,0 +1,3 @@
1
+ require_relative 'base_error'
2
+ require_relative 'invalid_format_error'
3
+ require_relative 'invalid_value_error'
@@ -0,0 +1,14 @@
1
+ # @author Matthieu CIAPPARA <ciappa_m@modulotech.fr>
2
+ # An exception representing an invalid format for a given field.
3
+ class Modulorails::InvalidFormatError < Modulorails::BaseError
4
+ # @!attribute r field
5
+ # The name of the field that had a wrong format.
6
+ attr_reader :field
7
+
8
+ # @param field [String]
9
+ def initialize(field)
10
+ super(I18n.t('modulorails.errors.invalid_format', field: field))
11
+
12
+ @field = field
13
+ end
14
+ end
@@ -0,0 +1,14 @@
1
+ # @author Matthieu CIAPPARA <ciappa_m@modulotech.fr>
2
+ # An exception representing an invalid value for a given field.
3
+ class Modulorails::InvalidValueError < Modulorails::BaseError
4
+ # @!attribute r field
5
+ # The name of the field that had a wrong value.
6
+ attr_reader :field
7
+
8
+ # @param field [String]
9
+ def initialize(field)
10
+ super(I18n.t('modulorails.errors.invalid_value', field: field))
11
+
12
+ @field = field
13
+ end
14
+ end
@@ -1,9 +1,38 @@
1
+ require_relative '../../app/helpers/modulorails/application_helper'
2
+
1
3
  module Modulorails
4
+ # Bind in the Rails lifecycle
2
5
  class Railtie < ::Rails::Railtie
3
- # Binding in the Rails lifecycle. Sending data after the initialization ensures we can access
4
- # all gems and symbols we might have to use.
6
+ # Update and add gems before we load the configuration
7
+ config.before_configuration do
8
+ # Currently, we limit everything to the development environment
9
+ if Rails.env.development?
10
+ # Check database configuration
11
+ Modulorails.generate_healthcheck_template
12
+ end
13
+ end
14
+
15
+ # Require the gem before we read the health_check initializer
16
+ config.before_initialize do
17
+ require 'health_check'
18
+ end
19
+
20
+ initializer 'modulorails.action_view' do
21
+ ActiveSupport.on_load :action_view do
22
+ include Modulorails::ApplicationHelper
23
+ end
24
+ end
25
+
26
+ initializer 'modulorails.assets' do |app|
27
+ %w[stylesheets javascripts].each do |subdirectory|
28
+ app.config.assets.paths << File.expand_path("../../../app/assets/#{subdirectory}", __FILE__)
29
+ end
30
+ end
31
+
32
+ # Sending data after the initialization ensures we can access
33
+ # all gems, constants and configurations we might need.
5
34
  config.after_initialize do
6
- # For now, we limit everything to the development environment
35
+ # Currently, we limit everything to the development environment
7
36
  if Rails.env.development?
8
37
  # Load translations
9
38
  I18n.load_path += [File.expand_path('../../../config/locales/en.yml', __FILE__)]
@@ -17,6 +46,9 @@ module Modulorails
17
46
  # Check database configuration
18
47
  Modulorails.check_database_config
19
48
 
49
+ # Add/update Rubocop config
50
+ Modulorails.generate_rubocop_template
51
+
20
52
  # Gem's self-update if a new version was released
21
53
  Modulorails.self_update
22
54
  end
@@ -0,0 +1,203 @@
1
+ # @author Matthieu CIAPPARA <ciappa_m@modulotech.fr>
2
+ # The base class for services. Should be implemented by ApplicationService following the model of
3
+ # ActiveRecord::Base and ApplicationRecord.
4
+ class Modulorails::BaseService
5
+ # Allow to instantiate the service and call the service in one go.
6
+ if Modulorails::COMPARABLE_RUBY_VERSION < Gem::Version.new('3.0')
7
+ def self.call(*args, &block)
8
+ new(*args, &block).call
9
+ end
10
+ else
11
+ def self.call(*args, **kwargs, &block)
12
+ new(*args, **kwargs, &block).call
13
+ end
14
+ end
15
+
16
+ # @abstract The main method to implement for your service to do something
17
+ def call
18
+ raise NotImplementedError.new('Implement method call on sub-class')
19
+ end
20
+
21
+ def to_s
22
+ self.class.to_s
23
+ end
24
+
25
+ # The method used by Modulorails::LogsForMethodService to log the service name
26
+ def to_tag
27
+ self.class.to_s
28
+ end
29
+
30
+ # Shamelessly copied from text_helper
31
+ def self.pluralize(count, singular, plural_arg = nil, plural: plural_arg, locale: I18n.locale)
32
+ word = if count == 1 || count =~ /^1(\.0+)?$/
33
+ singular
34
+ else
35
+ plural || singular.pluralize(locale)
36
+ end
37
+
38
+ "#{count || 0} #{word}"
39
+ end
40
+
41
+ protected
42
+
43
+ # Wrapper to Modulorails::LogsForMethodService
44
+ # @param method [#to_s] The method calling `#log`
45
+ # @param message [Hash,#to_s] The message to log; Hash will be logged after a #to_json call
46
+ def log(method, message)
47
+ Modulorails::LogsForMethodService.call(method: method, message: message, tags: [self])
48
+ end
49
+
50
+ # @yield Wrap the given block in an ActiveRecord transaction.
51
+ # @yieldreturn [Object] Will be available as data of the `SuccessData` returned by the method
52
+ # @return [SuccessData] If the transaction was not rollbacked; give access to the block's return.
53
+ # @return [ErrorData] If the transaction was rollbacked; give access to the rollbacking exception.
54
+ def with_transaction
55
+ data = nil
56
+
57
+ ActiveRecord::Base.transaction do
58
+ data = yield
59
+ end
60
+
61
+ SuccessData.new(data)
62
+ rescue ActiveRecord::RecordInvalid => e
63
+ # Known error, no need for a log, it just needs to be returned
64
+ ErrorData.new(e.message, exception: e)
65
+ rescue StandardError => e
66
+ # Unknown error, log the error
67
+ Rails.logger.error("#{self}: #{e.message}")
68
+ Rails.logger.error("Local variables: #{local_variables.map! { |v| { v => eval(v.to_s, binding) } }}")
69
+ Rails.logger.error(e.backtrace&.join("\n"))
70
+
71
+ # Return the error
72
+ ErrorData.new(e.message, exception: e)
73
+ end
74
+
75
+ # Cast the date/datetime parameters to time with zones.
76
+ # @param from [String,ActiveSupport::TimeWithZone] the minimum date
77
+ # @param to [String,ActiveSupport::TimeWithZone] the maximum date
78
+ # @return [[ActiveSupport::TimeWithZone, ActiveSupport::TimeWithZone]] The given dates casted.
79
+ def params_to_time(from, to = nil)
80
+ from = from.is_a?(String) && from.present? ? from.to_time_with_zone : from
81
+ to = if to.is_a?(String) && to.present?
82
+ to = to.to_time_with_zone
83
+
84
+ # If the right bound is exactly the same as the left one, we add 1 day to the right
85
+ # one by default.
86
+ to.present? && to == from ? to + 1.day : to
87
+ else
88
+ to
89
+ end
90
+
91
+ [from, to]
92
+ end
93
+
94
+ # Shamelessly copied from text_helper
95
+ def pluralize(count, singular, plural_arg = nil, plural: plural_arg, locale: I18n.locale)
96
+ self.class.pluralize(count, singular, plural_arg, plural: plural, locale: locale)
97
+ end
98
+
99
+ # Take a series of keys, dig them into the given params and ensure the found value is a date.
100
+ # @param keys [Array<Symbol>] The keys to find in the parameters.
101
+ # @param params [#dig] The parameters to dig in.
102
+ # @return [ActiveSupport::TimeWithZone] If it is found, the value casted as a datetime.
103
+ # @return [nil] If the parameter was not found.
104
+ def parse_date_field(*keys, params:)
105
+ value = params.dig(*keys)
106
+
107
+ return nil unless value
108
+
109
+ begin
110
+ Time.zone.parse(value)
111
+ rescue ArgumentError
112
+ raise InvalidFormatError.new(keys.join('/'))
113
+ end
114
+ end
115
+
116
+ # Take a series of keys, dig them into the given params and ensure the found value is a date.
117
+ # @param keys [Array<Symbol>] The keys to find in the parameters.
118
+ # @param params [#dig] The parameters to dig in.
119
+ # @return [ActiveSupport::TimeWithZone] If it is found, the value casted as a datetime.
120
+ # @return [nil] If the parameter was not found.
121
+ def parse_iso8601_field(*keys, params: {})
122
+ value = params.dig(*keys)
123
+
124
+ return nil unless value
125
+
126
+ begin
127
+ Time.zone.iso8601(value)
128
+ rescue ArgumentError
129
+ raise InvalidFormatError.new(keys.join('/'))
130
+ end
131
+ end
132
+
133
+ # Take a series of keys, dig them into the given params and ensure the found value is valid.
134
+ # @param allowed_values [#include?] Allowed values
135
+ # @param keys [Array<Symbol>] The keys to find in the parameters.
136
+ # @param params [#dig] The parameters to dig in.
137
+ # @param allow_nil [Boolean] Do not raise if value is nil.
138
+ # @return [ActiveSupport::TimeWithZone] If it is found, the value casted as a datetime.
139
+ # @return [nil] If the parameter was not found.
140
+ def parse_enumerated_field(allowed_values, keys, params: {}, allow_nil: true)
141
+ value = params.dig(*keys)
142
+
143
+ if value.respond_to?(:each)
144
+ raise InvalidValueError.new(keys.join('/')) unless value.all? { |v| allowed_values.include?(v) }
145
+ else
146
+ return nil if !value && allow_nil
147
+
148
+ raise InvalidValueError.new(keys.join('/')) unless allowed_values.include?(value)
149
+ end
150
+
151
+ value
152
+ end
153
+
154
+ # @param model [#find_by] The model to search in
155
+ # @param field [Symbol] The field to filter on
156
+ # @param keys [Array<Symbol>] The keys to search in the params
157
+ # @param params [#dig] The params to search in
158
+ # @param allow_nil [Boolean] Raise if the keys are not found; default true
159
+ # @return [#id, nil] The record corresponding to given field and values (through keys)
160
+ # @raise [InvalidValueError] When there is no record for given field and values
161
+ def parse_referential_value(model, field, *keys, params: {}, allow_nil: true)
162
+ value = params.dig(*keys)
163
+
164
+ unless value
165
+ if allow_nil
166
+ return nil
167
+ else
168
+ raise InvalidValueError.new(keys.join('/'))
169
+ end
170
+ end
171
+
172
+ result = model.find_by(field => value)
173
+
174
+ raise InvalidValueError.new(keys.join('/')) if result.nil?
175
+
176
+ result
177
+ end
178
+
179
+ # @param model [#where] The model to search in
180
+ # @param field [Symbol] The field to filter on
181
+ # @param keys [Array<Symbol>] The keys to search in the params
182
+ # @param params [#dig] The parmas to search in
183
+ # @param allow_nil [Boolean] Raise if the keys are not found; default true
184
+ # @return [#ids, nil] The record corresponding to given field and values (through keys)
185
+ # @raise [InvalidValueError] When there is no record for given field and values
186
+ def parse_referential_values(model, field, *keys, params: {}, allow_nil: true)
187
+ values = params.dig(*keys)
188
+
189
+ if values.blank?
190
+ if allow_nil
191
+ return nil
192
+ else
193
+ raise InvalidValueError.new(keys.join('/'))
194
+ end
195
+ end
196
+
197
+ results = model.where(field => values)
198
+
199
+ raise InvalidValueError.new(keys.join('/')) if results.blank?
200
+
201
+ results
202
+ end
203
+ end
@@ -0,0 +1,42 @@
1
+ # @author Matthieu CIAPPARA <ciappa_m@modulotech.fr>
2
+ # A service to write formatted debug logs from a method.
3
+ class Modulorails::LogsForMethodService < Modulorails::BaseService
4
+
5
+ # @param method [String] The name of the calling method
6
+ # @param message [String,#to_json] The body of the log.
7
+ # @param tags [Array<String,#to_tag>] A list of tags to prefix the log.
8
+ def initialize(method:, message:, tags: [])
9
+ super()
10
+ @method = method
11
+ @message = message
12
+ @tags = tags
13
+ end
14
+
15
+ # Write a formatted debug log using given initialization parameters
16
+ def call
17
+ # Map the tags (either objects responding to #to_tag or strings) to prefix the log body
18
+ tag_strings = @tags.map do |tag|
19
+ tag.respond_to?(:to_tag) ? "[#{tag.to_tag}]" : "[#{tag}]"
20
+ end
21
+
22
+ # If the message respond_to #to_json (and is not a String), use it.
23
+ @message = jsonify if !@message.is_a?(String) && @message.respond_to?(:to_json)
24
+
25
+ # Join the tags
26
+ tag_string = tag_strings.join
27
+
28
+ # Split on newlines to avoid a log of a thousand columns and for each line, prefix the tags
29
+ # and log it as debug.
30
+ @message.split("\n").each do |line|
31
+ msg = "#{tag_string}[#{@method}] #{line}"
32
+
33
+ Rails.logger.debug(msg)
34
+ end
35
+ end
36
+
37
+ private
38
+
39
+ def jsonify
40
+ @message.to_json
41
+ end
42
+ end
@@ -0,0 +1,2 @@
1
+ require_relative 'base_service'
2
+ require_relative 'logs_for_method_service'
@@ -0,0 +1,17 @@
1
+ # @author Matthieu Ciappara <ciappa_m@modulotech>
2
+ # A success resulting from an operation with optional additional data.
3
+ class Modulorails::SuccessData
4
+ # @!attribute r data
5
+ # An object to transport some data (for instance the result of the operation). Defaults to nil.
6
+ attr_reader :data
7
+
8
+ # @param data [Object] An object to transport some data (for instance the result of the operation)
9
+ def initialize(data=nil)
10
+ @data = data
11
+ end
12
+
13
+ # @return [true] A success always means the operation was a success. ;)
14
+ def success?
15
+ true
16
+ end
17
+ end
@@ -31,7 +31,7 @@ module Modulorails
31
31
 
32
32
  def call
33
33
  database_configuration = check_standard_config_file_location
34
- return false unless database_configuration
34
+ return [:standard_config_file_location] unless database_configuration
35
35
 
36
36
  check_test_database_not_equals_dev_database(database_configuration)
37
37
  check_rules_for_environment(database_configuration, :development)
@@ -52,13 +52,19 @@ module Modulorails
52
52
 
53
53
  def check_standard_config_file_location
54
54
  # Load the configuration
55
- config = Psych.load_file(Rails.root.join('config/database.yml'))
55
+ config = if Modulorails::COMPARABLE_RUBY_VERSION >= Gem::Version.new('3.1')
56
+ # Ruby 3.1 uses Psych4 which changes the default way of handling aliases in
57
+ # `load_file`.
58
+ Psych.load_file(Rails.root.join('config/database.yml'), aliases: true)
59
+ else
60
+ Psych.load_file(Rails.root.join('config/database.yml'))
61
+ end
56
62
 
57
63
  # If no exception was raised, then the database configuration file is at standard location
58
64
  @rules[:standard_config_file_location] = true
59
65
 
60
66
  config
61
- rescue StandardError =>e
67
+ rescue StandardError => e
62
68
  # An exception was raised, either the file is not a the standard location, either it just
63
69
  # cannot be read. Either way, we consider the config as invalid
64
70
  @rules[:standard_config_file_location] = false
@@ -1,3 +1,6 @@
1
1
  module Modulorails
2
- VERSION = '0.2.2'
2
+ VERSION = '1.0.1'
3
+
4
+ # Useful to compare the current Ruby version
5
+ COMPARABLE_RUBY_VERSION = Gem::Version.new(RUBY_VERSION)
3
6
  end