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.
- checksums.yaml +4 -4
- data/.dockerignore +14 -0
- data/.gitignore +3 -0
- data/.rubocop.yml +62 -0
- data/.travis.yml +23 -4
- data/Appraisals +18 -0
- data/CHANGELOG.md +42 -1
- data/Dockerfile.ruby25 +34 -0
- data/Dockerfile.ruby26 +28 -0
- data/Dockerfile.ruby27 +25 -0
- data/Dockerfile.ruby30 +25 -0
- data/Dockerfile.ruby31 +25 -0
- data/Gemfile.lock +59 -54
- data/README.md +16 -4
- data/Rakefile +1 -1
- data/app/assets/stylesheets/modulorails.css +21 -0
- data/app/helpers/modulorails/application_helper.rb +8 -0
- data/config/locales/en.yml +3 -0
- data/docker-compose.debug.yml +47 -0
- data/docker-compose.yml +37 -0
- data/entrypoints/appraisal_test.sh +7 -0
- data/gemfiles/rails_52.gemfile +9 -0
- data/gemfiles/rails_60.gemfile +9 -0
- data/gemfiles/rails_61.gemfile +9 -0
- data/gemfiles/rails_70.gemfile +9 -0
- data/lib/generators/modulorails/docker/docker_generator.rb +19 -0
- data/lib/generators/modulorails/docker/templates/Dockerfile.prod.tt +57 -0
- data/lib/generators/modulorails/docker/templates/Dockerfile.tt +26 -0
- data/lib/generators/modulorails/docker/templates/config/database.yml.tt +32 -0
- data/lib/generators/modulorails/docker/templates/docker-compose.prod.yml.tt +50 -0
- data/lib/generators/modulorails/docker/templates/docker-compose.yml.tt +71 -0
- data/lib/generators/modulorails/docker/templates/entrypoints/docker-entrypoint.sh.tt +20 -0
- data/lib/generators/modulorails/docker/templates/entrypoints/webpack-entrypoint.sh.tt +7 -0
- data/lib/generators/modulorails/gitlabci/gitlabci_generator.rb +34 -0
- data/lib/generators/modulorails/gitlabci/templates/.gitlab-ci.yml.tt +118 -0
- data/lib/generators/modulorails/gitlabci/templates/.modulorails-gitlab-ci +6 -0
- data/lib/generators/modulorails/gitlabci/templates/config/database-ci.yml.tt +8 -0
- data/lib/generators/modulorails/healthcheck/health_check_generator.rb +41 -0
- data/lib/generators/modulorails/healthcheck/templates/.modulorails-health_check +6 -0
- data/lib/generators/modulorails/healthcheck/templates/config/initializers/health_check.rb.tt +100 -0
- data/lib/generators/modulorails/rubocop/rubocop_generator.rb +24 -0
- data/lib/generators/modulorails/rubocop/templates/rubocop.yml.tt +18 -0
- data/lib/generators/modulorails/self_update/self_update_generator.rb +32 -0
- data/lib/generators/modulorails/service/service_generator.rb +13 -0
- data/lib/generators/modulorails/service/templates/service.rb.tt +28 -0
- data/lib/modulorails/configuration.rb +8 -2
- data/lib/modulorails/data.rb +21 -2
- data/lib/modulorails/error_data.rb +21 -0
- data/lib/modulorails/errors/base_error.rb +4 -0
- data/lib/modulorails/errors/errors.rb +3 -0
- data/lib/modulorails/errors/invalid_format_error.rb +14 -0
- data/lib/modulorails/errors/invalid_value_error.rb +14 -0
- data/lib/modulorails/railtie.rb +35 -3
- data/lib/modulorails/services/base_service.rb +203 -0
- data/lib/modulorails/services/logs_for_method_service.rb +42 -0
- data/lib/modulorails/services/services.rb +2 -0
- data/lib/modulorails/success_data.rb +17 -0
- data/lib/modulorails/validators/database_configuration.rb +9 -3
- data/lib/modulorails/version.rb +4 -1
- data/lib/modulorails.rb +46 -21
- data/modulorails.gemspec +4 -0
- metadata +114 -17
- data/lib/generators/gitlabci_generator.rb +0 -134
- data/lib/generators/templates/.gitlab-ci.yml +0 -72
- data/lib/generators/templates/.modulorails-gitlab-ci +0 -3
- data/lib/generators/templates/config/database-ci.yml +0 -7
- 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[
|
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
|
data/lib/modulorails/data.rb
CHANGED
@@ -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
|
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,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
|
data/lib/modulorails/railtie.rb
CHANGED
@@ -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
|
-
#
|
4
|
-
|
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
|
-
#
|
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,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
|
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 =
|
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
|